-
Notifications
You must be signed in to change notification settings - Fork 0
/
snaplet-hdbc.html
595 lines (441 loc) · 36.1 KB
/
snaplet-hdbc.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
<!DOCTYPE html>
<!--[if IEMobile 7 ]><html class="no-js iem7"><![endif]-->
<!--[if lt IE 9]><html class="no-js lte-ie8"><![endif]-->
<!--[if (gt IE 8)|(gt IEMobile 7)|!(IEMobile)|!(IE)]><!--><html class="no-js" lang="en"><!--<![endif]-->
<head>
<meta charset="utf-8">
<title>HDBC Snaplet Tutorial - Norm 2782</title>
<meta name="author" content="Jurriën Stutterheim">
<meta name="description" content="HDBC Snaplet Tutorial Jan 3rd, 2012 About the snaplet-hdbc library Many applications use a relational database for information storage. A popular …">
<!-- http://t.co/dKP3o1e -->
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="canonical" href="http://norm2782.github.com/snaplet-hdbc.html">
<link href="/favicon.png" rel="icon">
<link href="/stylesheets/screen.css" media="screen, projection" rel="stylesheet" type="text/css">
<script src="/javascripts/modernizr-2.0.js"></script>
<script src="/javascripts/ender.js"></script>
<script src="/javascripts/octopress.js" type="text/javascript"></script>
<link href="/atom.xml" rel="alternate" title="Norm 2782" type="application/atom+xml">
<!--Fonts from Google"s Web font directory at http://google.com/webfonts -->
<link href="http://fonts.googleapis.com/css?family=PT+Serif:regular,italic,bold,bolditalic" rel="stylesheet" type="text/css">
<link href="http://fonts.googleapis.com/css?family=PT+Sans:regular,italic,bold,bolditalic" rel="stylesheet" type="text/css">
</head>
<body >
<header role="banner"><hgroup>
<h1><a href="/">Norm 2782</a></h1>
<h2>Why are you here?</h2>
</hgroup>
</header>
<nav role="navigation"><ul class="subscription" data-subscription="rss">
<li><a href="/atom.xml" rel="subscribe-rss" title="subscribe via RSS">RSS</a></li>
</ul>
<form action="http://google.com/search" method="get">
<fieldset role="search">
<input type="hidden" name="q" value="site:norm2782.github.com" />
<input class="search" type="text" name="q" results="0" placeholder="Search"/>
</fieldset>
</form>
<ul class="main-navigation">
<li><a href="/">Blog</a></li>
<li><a href="/snaplet-hdbc.html">snaplet-hdbc</a></li>
<li><a href="/blog/archives">Archives</a></li>
</ul>
</nav>
<div id="main">
<div id="content">
<div>
<article role="article">
<header>
<h1 class="entry-title">HDBC Snaplet Tutorial</h1>
<p class="meta">
<time datetime="2012-01-03T08:23:00+01:00" pubdate data-updated="true">Jan 3<span>rd</span>, 2012</time></p>
</header>
<h2>About the snaplet-hdbc library</h2>
<p>Many applications use a relational database for information storage. A popular
library, called HDBC, already exists to make this possible. The snaplet-hdbc
library provides two snaplets to make it easier to integrate HDBC into your web
application: <code>Snap.Snaplet.Auth.Backends.Hdbc</code> and <code>Snap.Snaplet.Hdbc</code>. The
former serves as a backend for Snap’s own authentication snaplet, while the
latter provides an abstraction over the plain HDBC functions. This tutorial
assumes that you are familiar with writing your own snaplet. If you are not
familiar with snaplets yet, please consider studying the
<a href="http://snapframework.com/docs/tutorials/snaplets-tutorial">snaplet tutorial</a>
first.</p>
<p>In this tutorial we will write a small application that uses these two snaplets
to interact with an SQLite database. The ideas presented here are implemented
in <a href="https://github.com/norm2782/JCU">one of my applications</a>.</p>
<h2>Installing</h2>
<p>Installing the snaplet is easy: just do a <code>cabal install snaplet-hdbc</code> and
you’re all set.</p>
<h2>Literate Haskell</h2>
<p>This tutorial is written in Literate Haskell, so we need to enable some
extensions and define our imports first.</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
</pre></td><td class='code'><pre><code class='haskell'><span class='line'><span class="o">></span> <span class="cm">{-# LANGUAGE TemplateHaskell #-}</span>
</span><span class='line'><span class="o">></span> <span class="cm">{-# LANGUAGE FlexibleInstances #-}</span>
</span><span class='line'><span class="o">></span> <span class="cm">{-# LANGUAGE MultiParamTypeClasses #-}</span>
</span><span class='line'><span class="o">></span> <span class="cm">{-# LANGUAGE OverloadedStrings #-}</span>
</span><span class='line'><span class="o">></span>
</span><span class='line'><span class="o">></span> <span class="kr">module</span> <span class="nn">Application</span> <span class="kr">where</span>
</span><span class='line'><span class="o">></span>
</span><span class='line'><span class="o">></span> <span class="kr">import</span> <span class="nn">Control.Monad</span>
</span><span class='line'><span class="o">></span> <span class="kr">import</span> <span class="nn">Control.Monad.State</span>
</span><span class='line'><span class="o">></span> <span class="kr">import</span> <span class="nn">Data.ByteString.Char8</span> <span class="p">(</span><span class="kt">ByteString</span><span class="p">)</span>
</span><span class='line'><span class="o">></span> <span class="kr">import</span> <span class="k">qualified</span> <span class="nn">Data.ByteString.Char8</span> <span class="k">as</span> <span class="n">BS</span>
</span><span class='line'><span class="o">></span> <span class="kr">import</span> <span class="nn">Data.Lens.Template</span>
</span><span class='line'><span class="o">></span> <span class="kr">import</span> <span class="nn">Data.Map</span> <span class="p">((</span><span class="o">!</span><span class="p">))</span>
</span><span class='line'><span class="o">></span> <span class="kr">import</span> <span class="nn">Data.Maybe</span>
</span><span class='line'><span class="o">></span> <span class="kr">import</span> <span class="nn">Data.String</span>
</span><span class='line'><span class="o">></span> <span class="kr">import</span> <span class="nn">Database.HDBC.Sqlite3</span>
</span><span class='line'><span class="o">></span> <span class="kr">import</span> <span class="nn">Snap.Core</span>
</span><span class='line'><span class="o">></span> <span class="kr">import</span> <span class="nn">Snap.Snaplet</span>
</span><span class='line'><span class="o">></span> <span class="kr">import</span> <span class="nn">Snap.Snaplet.Auth</span>
</span><span class='line'><span class="o">></span> <span class="kr">import</span> <span class="nn">Snap.Snaplet.Auth.Backends.Hdbc</span>
</span><span class='line'><span class="o">></span> <span class="kr">import</span> <span class="nn">Snap.Snaplet.Hdbc</span>
</span><span class='line'><span class="o">></span> <span class="kr">import</span> <span class="nn">Snap.Snaplet.Session</span>
</span><span class='line'><span class="o">></span> <span class="kr">import</span> <span class="nn">Snap.Snaplet.Session.Backends.CookieSession</span>
</span></code></pre></td></tr></table></div></figure>
<h2>Snaplet state</h2>
<p>Our goal is to be able to authenticate against a database, remember the fact
that we are logged in and then retrieve some information from the database.
Before we can do so, we need to define our snaplet’s state and generate the
corresponding lenses:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class='haskell'><span class='line'><span class="o">></span> <span class="kr">data</span> <span class="kt">App</span>
</span><span class='line'><span class="o">></span> <span class="ow">=</span> <span class="kt">App</span>
</span><span class='line'><span class="o">></span> <span class="p">{</span> <span class="n">_authLens</span> <span class="ow">::</span> <span class="kt">Snaplet</span> <span class="p">(</span><span class="kt">AuthManager</span> <span class="kt">App</span><span class="p">)</span>
</span><span class='line'><span class="o">></span> <span class="p">,</span> <span class="n">_sessLens</span> <span class="ow">::</span> <span class="kt">Snaplet</span> <span class="kt">SessionManager</span>
</span><span class='line'><span class="o">></span> <span class="p">,</span> <span class="n">_dbLens</span> <span class="ow">::</span> <span class="kt">Snaplet</span> <span class="p">(</span><span class="kt">HdbcSnaplet</span> <span class="kt">Connection</span> <span class="kt">IO</span><span class="p">)</span>
</span><span class='line'><span class="o">></span> <span class="p">}</span>
</span><span class='line'><span class="o">></span>
</span><span class='line'><span class="o">></span> <span class="n">makeLens</span> <span class="sc">''</span><span class="kt">App</span>
</span></code></pre></td></tr></table></div></figure>
<p>The <code>authLens</code> allows us to do the actual authentication, while the <code>sessLens</code>
allows us to remember the result of the authentication attempt. As you might
have guessed, the <code>dbLens</code> allows us to interact with the database. The
<code>AuthManager</code>, <code>SessionManager</code> and <code>HdbcSnaplet</code> types are provided by the
corresponding snaplets, while the <code>Connection</code> type is provided by HDBC. In
this case, <code>Connection</code> represents a connection to our SQLite database.</p>
<p>After having defined the application state, we can start writing our
initialiser:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class='haskell'><span class='line'><span class="o">></span> <span class="n">tutorialAppInit</span> <span class="ow">::</span> <span class="kt">SnapletInit</span> <span class="kt">App</span> <span class="kt">App</span>
</span><span class='line'><span class="o">></span> <span class="n">tutorialAppInit</span> <span class="ow">=</span> <span class="n">makeSnaplet</span> <span class="s">"snaplet-hdbc-tutorial"</span>
</span><span class='line'><span class="o">></span> <span class="s">"A tutorial snaplet showing the use of snaplet-hdbc"</span> <span class="kt">Nothing</span> <span class="o">$</span> <span class="kr">do</span>
</span><span class='line'><span class="o">></span> <span class="n">addRoutes</span> <span class="p">[</span> <span class="p">(</span><span class="s">"/some/:num"</span><span class="p">,</span> <span class="n">someNumHandler</span><span class="p">)</span> <span class="p">]</span>
</span><span class='line'><span class="o">></span> <span class="n">_sesslens'</span> <span class="ow"><-</span> <span class="n">nestSnaplet</span> <span class="s">"session"</span> <span class="n">sessLens</span> <span class="o">$</span> <span class="n">initCookieSessionManager</span>
</span><span class='line'><span class="o">></span> <span class="s">"config/site_key.txt"</span> <span class="s">"_session"</span> <span class="kt">Nothing</span>
</span><span class='line'><span class="o">></span> <span class="kr">let</span> <span class="n">sqli</span> <span class="ow">=</span> <span class="n">connectSqlite3</span> <span class="s">"resources/tutorial.db"</span>
</span><span class='line'><span class="o">></span> <span class="n">_dblens'</span> <span class="ow"><-</span> <span class="n">nestSnaplet</span> <span class="s">"hdbc"</span> <span class="n">dbLens</span> <span class="o">$</span> <span class="n">hdbcInit</span> <span class="n">sqli</span>
</span><span class='line'><span class="o">></span> <span class="n">_authlens'</span> <span class="ow"><-</span> <span class="n">nestSnaplet</span> <span class="s">"auth"</span> <span class="n">authLens</span> <span class="o">$</span> <span class="n">initHdbcAuthManager</span>
</span><span class='line'><span class="o">></span> <span class="n">defAuthSettings</span> <span class="n">sessLens</span> <span class="n">sqli</span> <span class="n">defAuthTable</span> <span class="n">defQueries</span>
</span><span class='line'><span class="o">></span> <span class="n">return</span> <span class="o">$</span> <span class="kt">App</span> <span class="n">_authlens'</span> <span class="n">_sesslens'</span> <span class="n">_dblens'</span>
</span></code></pre></td></tr></table></div></figure>
<p>Many things are happening in this initialiser. First we define a route and a
fallback handler, after which we initialise a session manager which stores the
session information in a cookie. We then use the <code>connectSqlite3</code> function from
HDBC to make a connection to the <code>tutorial.db</code> file locate in the <code>resources</code>
directory, which sits in our project directory. This gives us a value of type
<code>IO Connection</code> in <code>sqli</code>. We use this raw connection to initialise both the
HDBC snaplet and the HDBC authentication snaplet, which happens on the
following two lines.</p>
<p>Initialising the HDBC snaplet is simple enough. We just pass the SQLite
connection we obtained earlier to the initialiser and use to usual
<code>nestSnaplet</code> function to nest the HDBC snaplet in our application snaplet. The
HDBC authentication snaplet on the other hand, has several parameters. Lets go
through them one by one:</p>
<ul>
<li><code>defAuthSettings</code> is exposed by the authentication snaplets and offers a
default value of type <code>AuthSettings</code>, which contains information about your
sessions.</li>
<li><code>sessLens</code> is the session lens that has been generated by Template Haskell
using the <code>makeLens</code> function.</li>
<li><code>sqli</code> is the raw database connection we have defined earlier in the
initialiser.</li>
<li><code>defAuthTable</code> is exposed by the HDBC authentication snaplet and it
provides a default set of column names for the table against you will
authenticate. It is of type <code>AuthTable</code>.</li>
<li><code>defQueries</code> is again exposed by the HDBC authentication snaplet and
contains a default set of functions which generate the SQL queries the
snaplet uses for the authentication process. Its type is <code>Queries</code>.</li>
</ul>
<p>By separating the column names from the actual queries, it becomes possible to
support varying use-cases. In the simplest case, the application uses the
default table layout and the default queries. When different column names are
desired, one will only need to provide a custom value of type <code>AuthTable</code> and
the queries will automatically use these new names. If completely custom
queries are desired, one can easily override them by providing a custom value
of type <code>Queries</code>. This allows for, e.g., using stored procedures for
authentication. This tutorial will not cover authentication (that’s another
tutorial), nor overriding the default queries. Please see the snaplet’s API
documentation and code to learn how to do that.</p>
<p>After all lenses are initialised, the <code>App</code> constructor is applied to all newly
created lenses and the initialiser’s work is done.</p>
<h2>Querying the database</h2>
<p>Now that the snaplet is initialised, we can start querying our database.
Amongst other functions, the HDBC snaplet offers the <code>query</code> and <code>query'</code>
functions:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='haskell'><span class='line'><span class="nf">query</span> <span class="ow">::</span> <span class="kt">HasHdbc</span> <span class="n">m</span> <span class="n">c</span> <span class="n">s</span> <span class="ow">=></span> <span class="kt">String</span> <span class="ow">-></span> <span class="p">[</span><span class="kt">SqlValue</span><span class="p">]</span> <span class="ow">-></span> <span class="n">m</span> <span class="p">[</span><span class="kt">Row</span><span class="p">]</span>
</span><span class='line'><span class="nf">query'</span> <span class="ow">::</span> <span class="kt">HasHdbc</span> <span class="n">m</span> <span class="n">c</span> <span class="n">s</span> <span class="ow">=></span> <span class="kt">String</span> <span class="ow">-></span> <span class="p">[</span><span class="kt">SqlValue</span><span class="p">]</span> <span class="ow">-></span> <span class="n">m</span> <span class="kt">Integer</span>
</span></code></pre></td></tr></table></div></figure>
<p>These functions make it easy to execute basic queries. The first one is
intended to be used for <code>SELECT</code> queries. It takes a <code>String</code> with the query
and a list of <code>SqlValue</code>s. The <code>SqlValue</code>s will be inserted on the places where
there is have a <code>?</code> in the query. The result of the query is a list of <code>Row</code>s.
A row is nothing more than a type synonym for a <code>Map</code>:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='haskell'><span class='line'><span class="kr">type</span> <span class="kt">Row</span> <span class="ow">=</span> <span class="kt">Map</span> <span class="kt">String</span> <span class="kt">SqlValue</span>
</span></code></pre></td></tr></table></div></figure>
<p>The second one is intended to be used with any other type of query, usually
the <code>INSERT</code>, <code>UPDATE</code> and <code>DELETE</code> queries.</p>
<p>Now suppose we have some table containing messages, we can now define a
function to retrieve them based on the value of some integer:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class='haskell'><span class='line'><span class="o">></span> <span class="kr">data</span> <span class="kt">Message</span> <span class="ow">=</span> <span class="kt">Message</span> <span class="kt">String</span> <span class="kr">deriving</span> <span class="kt">Show</span>
</span><span class='line'><span class="o">></span>
</span><span class='line'><span class="o">></span> <span class="n">getMessages</span> <span class="ow">::</span> <span class="kt">HasHdbc</span> <span class="n">m</span> <span class="n">c</span> <span class="n">s</span> <span class="ow">=></span> <span class="kt">ByteString</span> <span class="ow">-></span> <span class="n">m</span> <span class="p">[</span><span class="kt">Message</span><span class="p">]</span>
</span><span class='line'><span class="o">></span> <span class="n">getMessages</span> <span class="n">n</span> <span class="ow">=</span> <span class="kr">do</span>
</span><span class='line'><span class="o">></span> <span class="n">rows</span> <span class="ow"><-</span> <span class="n">query</span> <span class="s">"SELECT * FROM messages WHERE somenum = ?"</span> <span class="p">[</span><span class="n">toSql</span> <span class="n">n</span><span class="p">]</span>
</span><span class='line'><span class="o">></span> <span class="n">return</span> <span class="o">$</span> <span class="n">map</span> <span class="n">toMsg</span> <span class="n">rows</span>
</span><span class='line'><span class="o">></span> <span class="kr">where</span> <span class="n">toMsg</span> <span class="ow">::</span> <span class="kt">Row</span> <span class="ow">-></span> <span class="kt">Message</span>
</span><span class='line'><span class="o">></span> <span class="n">toMsg</span> <span class="n">rw</span> <span class="ow">=</span> <span class="kt">Message</span> <span class="o">$</span> <span class="n">fromSql</span> <span class="p">(</span><span class="n">rw</span> <span class="o">!</span> <span class="s">"msgcol"</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>
<p>Normally you would expect to apply this function in your application’s
handlers, but these query functions are not of type <code>Handler</code>, nor are they of
type <code>MonadSnap</code>! Instead, we expect our query to be executed in the context of
the <code>HasHdbc</code> typeclass. What is that all about?</p>
<h2>The HasHdbc typeclass</h2>
<p>One of the goals when designing the HDBC snaplet was to separate the functions
from the <code>Handler</code> monad, or any Snap-related monad, so that the same queries
could also be run outside of a Snap context, for example in a command-line
tool. This goal is accomplished by the <code>HasHdbc</code> class. Whenever we are in some
monad and a corresponding connection for which this class is defined, we can
use the functions provided by the snaplet. In order to be able to use the
snaplet’s functions in our application handlers, we need to make sure that our
application’s handlers become an instance of <code>HasHdbc</code>. Before we can do that,
however, we need to understand the <code>HasHdbc</code> typeclass:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='haskell'><span class='line'><span class="kr">class</span> <span class="p">(</span> <span class="kt">IConnection</span> <span class="n">c</span>
</span><span class='line'> <span class="p">,</span> <span class="kt">ConnSrc</span> <span class="n">s</span>
</span><span class='line'> <span class="p">,</span> <span class="kt">MonadCatchIO</span> <span class="n">m</span>
</span><span class='line'> <span class="p">)</span>
</span><span class='line'> <span class="ow">=></span> <span class="kt">HasHdbc</span> <span class="n">m</span> <span class="n">c</span> <span class="n">s</span> <span class="o">|</span> <span class="n">m</span> <span class="ow">-></span> <span class="n">c</span> <span class="n">s</span> <span class="kr">where</span>
</span><span class='line'> <span class="n">getHdbcState</span> <span class="ow">::</span> <span class="n">m</span> <span class="p">(</span><span class="kt">HdbcSnaplet</span> <span class="n">c</span> <span class="n">s</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>
<p>Again there are many things happening on only three lines of code. First we see
that the typeclass is parameterised by three type variables, <code>m</code>, <code>c</code> and <code>s</code>.
The <code>m</code> is any monad for which there is a <code>MonadCatchIO</code> instance available.
As it so happens, <code>Handler</code> has an instance for this out of the box. The second
parameter needs to be some type for which we also have an instance of
<code>IConnection</code>, which is a typeclass provided by HDBC. The <code>Connection</code> type we
saw earlier is such a type. The <code>s</code> parameter needs to be something of type
<code>ConnSrc</code>; some context from which we can get a new connection. By default, the
snaplet provides <code>ConnSrc</code> instances for <code>IO</code> and <code>Pool</code> (from the
<code>resource-pool</code> package; it offers resource pooling capabilities). Since our
example state type defines <code>s</code> to be <code>IO</code>, we don’t have to worry about this
parameter anymore. Finally, the functional dependency <code>m -> c s</code> says: “if we
know the type of <code>m</code>, we also know the corresponding types of <code>c</code> and <code>s</code>”. Or:
”<code>m</code> uniquely determines <code>c</code> and <code>s</code>”. This is very powerful, because we can
now do things with a connection, even if we only know in which monad we are.
The downside is that we cannot use two different adapter types with our
application handlers. The typeclass defines an expression <code>getHdbcState</code>, which
only requires you to yield the snaplet’s state type in the context of monad <code>m</code>.</p>
<p>Getting back to our application, we want to define an instance of <code>HasHdbc</code> for
our application’s handlers and we want the <code>getHdbcState</code> expression to give us
something of type <code>HdbcSnaplet Connection IO</code> in the context of these handlers.
A common type for an application handler would be <code>Handler App App</code>. With that
in mind, lets see what the <code>getHdbcState</code> type would look like if we were to
instantiate the typeclass:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='haskell'><span class='line'><span class="nf">getHdbcState</span> <span class="ow">::</span> <span class="kt">Handler</span> <span class="kt">App</span> <span class="kt">App</span> <span class="p">(</span><span class="kt">HdbcSnaplet</span> <span class="kt">Connection</span> <span class="kt">IO</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>
<p>It looks like we’re just defining a regular <code>Handler</code>, like we always do in our
Snap applications, but where do we get an <code>HdbcSnaplet Connection IO</code>? It turns
out that this is exactly the HDBC snaplet’s state type, so all we need to do is
get the state from the snaplet using the <code>dbLens</code> and the state monad’s <code>get</code>
function:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='haskell'><span class='line'><span class="kr">instance</span> <span class="kt">HasHdbc</span> <span class="p">(</span><span class="kt">Handler</span> <span class="kt">App</span> <span class="kt">App</span><span class="p">)</span> <span class="kt">Connection</span> <span class="kt">IO</span> <span class="kr">where</span>
</span><span class='line'> <span class="n">getHdbcState</span> <span class="ow">=</span> <span class="n">with</span> <span class="n">dbLens</span> <span class="n">get</span>
</span></code></pre></td></tr></table></div></figure>
<p>And we’re done! Now we can interact with the database in our handlers, using
the functions provided by the HDBC snaplet.</p>
<h2>Putting it all together</h2>
<p>Now that we have all the basics, we can finally write a handler which interacts
with the database. Lets create the <code>someNumHandler</code> from the example
initialiser. It reads an integer from the URL and uses that integer to
parameterise a database query:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='haskell'><span class='line'><span class="o">></span> <span class="n">someNumHandler</span> <span class="ow">::</span> <span class="kt">Handler</span> <span class="kt">App</span> <span class="kt">App</span> <span class="nb">()</span>
</span><span class='line'><span class="o">></span> <span class="n">someNumHandler</span> <span class="ow">=</span> <span class="kr">do</span>
</span><span class='line'><span class="o">></span> <span class="n">mnum</span> <span class="ow"><-</span> <span class="n">getParam</span> <span class="s">"num"</span>
</span><span class='line'><span class="o">></span> <span class="kr">let</span> <span class="n">n</span> <span class="ow">=</span> <span class="n">fromMaybe</span> <span class="s">"11"</span> <span class="n">mnum</span>
</span><span class='line'><span class="o">></span> <span class="n">msgs</span> <span class="ow"><-</span> <span class="n">getMessages</span> <span class="n">n</span>
</span><span class='line'><span class="o">></span> <span class="n">writeBS</span> <span class="o">.</span> <span class="kt">BS</span><span class="o">.</span><span class="n">pack</span> <span class="o">$</span> <span class="n">show</span> <span class="n">msgs</span>
</span></code></pre></td></tr></table></div></figure>
<p>Since <code>Handler</code> now has a <code>HasHdbc</code> instance, we can happily execute our
queries from the context of our handlers.</p>
<h2>Wrapping up</h2>
<p>If you are already a bit more familiar with snaplets, you might have notices
that the <code>HasHdbc</code> instance for <code>Handler</code> is not as general as it could be.
Since our implementation of <code>getConnSrc</code> is only interested in the current
snaplet’s state, we can leave the type of the first parameter to <code>Handler</code>
variable. This gives us the instance:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='haskell'><span class='line'><span class="o">></span> <span class="kr">instance</span> <span class="kt">HasHdbc</span> <span class="p">(</span><span class="kt">Handler</span> <span class="n">b</span> <span class="kt">App</span><span class="p">)</span> <span class="kt">Connection</span> <span class="kt">IO</span> <span class="kr">where</span>
</span><span class='line'><span class="o">></span> <span class="n">getHdbcState</span> <span class="ow">=</span> <span class="n">with</span> <span class="n">dbLens</span> <span class="n">get</span>
</span></code></pre></td></tr></table></div></figure>
<p>That’s it! You can now write web applications, backed by an HDBC-supported
database of your choosing.</p>
<h2>Using <code>resource-pool</code></h2>
<p>The snaplet comes with support for the <code>resource-pool</code> package built-in. If you
wish to use it, change the type of the subsnaplet from</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='haskell'><span class='line'> <span class="p">,</span> <span class="n">_dbLens</span> <span class="ow">::</span> <span class="kt">Snaplet</span> <span class="p">(</span><span class="kt">HdbcSnaplet</span> <span class="kt">Connection</span> <span class="kt">IO</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>
<p>to</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='haskell'><span class='line'> <span class="p">,</span> <span class="n">_dbLens</span> <span class="ow">::</span> <span class="kt">Snaplet</span> <span class="p">(</span><span class="kt">HdbcSnaplet</span> <span class="kt">Connection</span> <span class="kt">Pool</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>
<p>and do not forget to import <code>Data.Pool</code>.</p>
<footer>
<p class="meta">
<time datetime="2012-01-03T08:23:00+01:00" pubdate data-updated="true">Jan 3<span>rd</span>, 2012</time>
</p>
<div class="sharing">
<a href="http://twitter.com/share" class="twitter-share-button" data-url="http://norm2782.github.com/snaplet-hdbc.html" data-via="" data-counturl="http://norm2782.github.com/snaplet-hdbc.html" >Tweet</a>
<div class="g-plusone" data-size="medium"></div>
</div>
</footer>
</article>
</div>
<aside class="sidebar">
<section>
<h1>Recent Posts</h1>
<ul id="recent_posts">
<li class="post">
<a href="/blog/2012/01/01/using-digestive-functors-with-heist/">Using digestive-functors with Heist</a>
</li>
</ul>
</section>
<section>
<h1>Github Repos</h1>
<ul id="gh_repos">
<li class="loading">Status updating...</li>
</ul>
<a href="https://github.com/norm2782">@norm2782</a> on Github
<script type="text/javascript">
$.domReady(function(){
if (!window.jXHR){
var jxhr = document.createElement('script');
jxhr.type = 'text/javascript';
jxhr.src = '/javascripts/libs/jXHR.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(jxhr, s);
}
github.showRepos({
user: 'norm2782',
count: 0,
skip_forks: true,
target: '#gh_repos'
});
});
</script>
<script src="/javascripts/github.js" type="text/javascript"> </script>
</section>
<section class="googleplus">
<h1>
<a href="https://plus.google.com/112856651265635312533?rel=author">
<img src="http://www.google.com/images/icons/ui/gprofile_button-32.png" width="32" height="32">
Google+
</a>
</h1>
</section>
</aside>
</div>
</div>
<footer role="contentinfo"><p>
Copyright © 2012 - Jurriën Stutterheim -
<span class="credit">Powered by <a href="http://octopress.org">Octopress</a></span>
</p>
</footer>
<script type="text/javascript">
(function() {
var script = document.createElement('script'); script.type = 'text/javascript'; script.async = true;
script.src = 'https://apis.google.com/js/plusone.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(script, s);
})();
</script>
<script type="text/javascript">
(function(){
var twitterWidgets = document.createElement('script');
twitterWidgets.type = 'text/javascript';
twitterWidgets.async = true;
twitterWidgets.src = 'http://platform.twitter.com/widgets.js';
document.getElementsByTagName('head')[0].appendChild(twitterWidgets);
})();
</script>
</body>
</html>