Adding SQL section
svn: r11271
This commit is contained in:
parent
940de216f0
commit
d4d703420d
10
collects/web-server/scribblings/tutorial/dummy-sqlite.ss
Normal file
10
collects/web-server/scribblings/tutorial/dummy-sqlite.ss
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
#lang scheme/base
|
||||||
|
|
||||||
|
(define db? #f)
|
||||||
|
(define sqlite:open #f)
|
||||||
|
(define sqlite:exec/ignore #f)
|
||||||
|
(define sqlite:select #f)
|
||||||
|
(define sqlite:insert #f)
|
||||||
|
|
||||||
|
(provide (all-defined-out))
|
||||||
|
|
|
@ -915,7 +915,179 @@ And our application is:
|
||||||
|
|
||||||
This approach to persistence can work surprisingly well for simple applications. But as our application's needs
|
This approach to persistence can work surprisingly well for simple applications. But as our application's needs
|
||||||
grow, we will have to deal with concurrency issues, the lack of a simply query language over our data model, etc.
|
grow, we will have to deal with concurrency issues, the lack of a simply query language over our data model, etc.
|
||||||
So, in a future tutorial, we'll talk about how to use an SQL database to store our blog model.
|
So, in the next section, we'll talk about how to use an SQL database to store our blog model.
|
||||||
|
|
||||||
|
@section{Using an SQL database}
|
||||||
|
@declare-exporting[#:use-sources (web-server/scribblings/tutorial/examples/iteration-10
|
||||||
|
web-server/scribblings/tutorial/examples/model-3
|
||||||
|
web-server/scribblings/tutorial/dummy-sqlite)]
|
||||||
|
@(require (for-label web-server/scribblings/tutorial/dummy-sqlite))
|
||||||
|
@;@(require (prefix-in sqlite: (for-label (planet jaymccarthy/sqlite:3/sqlite))))
|
||||||
|
|
||||||
|
Our next task is to employ an SQL database for the blog model. We'll be using SQLite with the @schememodname[(planet jaymccarthy/sqlite:3/sqlite)] PLaneT package. We add the following to the top of our model:
|
||||||
|
|
||||||
|
@schemeblock[
|
||||||
|
(require (prefix-in sqlite: (planet jaymccarthy/sqlite:3/sqlite)))
|
||||||
|
]
|
||||||
|
|
||||||
|
We now have the following bindings:
|
||||||
|
|
||||||
|
@defthing[db? (any/c . -> . boolean?)]
|
||||||
|
@defthing[sqlite:open (path? . -> . db?)]
|
||||||
|
@defthing[sqlite:exec/ignore (db? string? . -> . void)]
|
||||||
|
@defthing[sqlite:select (db? string? . -> . (listof vector?))]
|
||||||
|
@defthing[sqlite:insert (db? string? . -> . integer?)]
|
||||||
|
|
||||||
|
|
||||||
|
The first thing we should do is decide on the relational structure of our model. We will use the following tables:
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
CREATE TABLE posts (id INTEGER PRIMARY KEY, title TEXT, body TEXT)
|
||||||
|
CREATE TABLE comments (pid INTEGER, content TEXT)
|
||||||
|
}
|
||||||
|
|
||||||
|
Each post will have an identifier, a title, and a body. This is the same as our old Scheme structure,
|
||||||
|
except we've added the identifier. (Actually, there was always an identifier---the memory pointer---but now
|
||||||
|
we have to make it explicit in the database.)
|
||||||
|
|
||||||
|
Each comment is tied to a post by the post's identifier and has textual content. We could have chosen to
|
||||||
|
serialize comments with @scheme[write] and add a new TEXT column to the posts table to store the value.
|
||||||
|
By adding a new comments table, we are more in accord with the relational style.
|
||||||
|
|
||||||
|
A @scheme[blog] structure will simply be a container for the database handle:
|
||||||
|
|
||||||
|
@defstruct[blog ([db db?])]
|
||||||
|
|
||||||
|
@bold{Exercise.} Write the @scheme[blog] structure definition. (It does not need to be mutable or serializable.)
|
||||||
|
|
||||||
|
We can now write the code to initialize a @scheme[blog] structure:
|
||||||
|
@schemeblock[
|
||||||
|
@code:comment{initialize-blog! : path? -> blog?}
|
||||||
|
@code:comment{Sets up a blog database (if it doesn't exist)}
|
||||||
|
(define (initialize-blog! home)
|
||||||
|
(define db (sqlite:open home))
|
||||||
|
(define the-blog (make-blog db))
|
||||||
|
(with-handlers ([exn? (lambda (exn) (void))])
|
||||||
|
(sqlite:exec/ignore db
|
||||||
|
(string-append
|
||||||
|
"CREATE TABLE posts"
|
||||||
|
"(id INTEGER PRIMARY KEY,"
|
||||||
|
"title TEXT, body TEXT)"))
|
||||||
|
(blog-insert-post!
|
||||||
|
the-blog "First Post" "This is my first post")
|
||||||
|
(blog-insert-post!
|
||||||
|
the-blog "Second Post" "This is another post")
|
||||||
|
(sqlite:exec/ignore
|
||||||
|
db "CREATE TABLE comments (pid INTEGER, content TEXT)")
|
||||||
|
(post-insert-comment!
|
||||||
|
the-blog (first (blog-posts the-blog))
|
||||||
|
"First comment!"))
|
||||||
|
the-blog)
|
||||||
|
]
|
||||||
|
|
||||||
|
@scheme[sqlite:open] will create a database if one does not already exist at the @scheme[home] path. But, we still need
|
||||||
|
to initialize the database with the table definitions and initial data.
|
||||||
|
|
||||||
|
We used @scheme[blog-insert-post!] and @scheme[post-insert-comment!] to initialize the database. Let's see their implementation:
|
||||||
|
|
||||||
|
@schemeblock[
|
||||||
|
@code:comment{blog-insert-post!: blog? string? string? -> void}
|
||||||
|
@code:comment{Consumes a blog and a post, adds the post at the top of the blog.}
|
||||||
|
(define (blog-insert-post! a-blog title body)
|
||||||
|
(sqlite:insert
|
||||||
|
(blog-db a-blog)
|
||||||
|
(format "INSERT INTO posts (title, body) VALUES ('~a', '~a')"
|
||||||
|
title body)))
|
||||||
|
|
||||||
|
@code:comment{post-insert-comment!: blog? post string -> void}
|
||||||
|
@code:comment{Consumes a blog, a post and a comment string. As a side-effect,}
|
||||||
|
@code:comment{adds the comment to the bottom of the post's list of comments.}
|
||||||
|
(define (post-insert-comment! a-blog p a-comment)
|
||||||
|
(sqlite:insert
|
||||||
|
(blog-db a-blog)
|
||||||
|
(format
|
||||||
|
"INSERT INTO comments (pid, content) VALUES ('~a', '~a')"
|
||||||
|
(post-id p) a-comment)))
|
||||||
|
]
|
||||||
|
|
||||||
|
@bold{Exercise.} Find the security hole common to these two functions.
|
||||||
|
|
||||||
|
@centerline{------------}
|
||||||
|
|
||||||
|
A user could submit a post with a title like, @scheme{null', 'null') and INSERT INTO accounts (username, password) VALUES ('ur','hacked} and get our simple @scheme[sqlite:insert] to make two INSERTs instead of one.
|
||||||
|
|
||||||
|
This is called an SQL injection attack. It can be resolved by using
|
||||||
|
prepared statements that let SQLite do the proper quoting for us. Refer
|
||||||
|
to the SQLite package documentation for usage.
|
||||||
|
|
||||||
|
@centerline{------------}
|
||||||
|
|
||||||
|
In @scheme[post-insert-comment!], we used @scheme[post-id], but we have not yet defined the new @scheme[post] structure.
|
||||||
|
It @emph{seems} like a @scheme[post] should be represented by an integer id, because the post table contains an integer as the identifying value.
|
||||||
|
|
||||||
|
However, we cannot tell from this structure
|
||||||
|
what blog this posts belongs to, and therefore, what database; so, we could not extract the title or body values,
|
||||||
|
since we do not know what to query. Therefore, we should associate the blog with each post:
|
||||||
|
|
||||||
|
@defstruct[post ([blog blog?] [id integer?])]
|
||||||
|
|
||||||
|
@bold{Exercise.} Write the structure definition for posts.
|
||||||
|
|
||||||
|
The only function that creates posts is @scheme[blog-posts]:
|
||||||
|
|
||||||
|
@schemeblock[
|
||||||
|
@code:comment{blog-posts : blog -> (listof post?)}
|
||||||
|
@code:comment{Queries for the post ids}
|
||||||
|
(define (blog-posts a-blog)
|
||||||
|
(map (compose (lambda (n) (make-post a-blog n))
|
||||||
|
string->number
|
||||||
|
(lambda (v) (vector-ref v 0)))
|
||||||
|
(rest (sqlite:select (blog-db a-blog)
|
||||||
|
"SELECT id FROM posts"))))
|
||||||
|
]
|
||||||
|
|
||||||
|
@scheme[sqlite:select] returns a list of vectors. The first element of the list is the name of the columns.
|
||||||
|
Each vector has one element for each column. Each element is a string representation of the value.
|
||||||
|
|
||||||
|
At this point we can write the functions that operate on posts:
|
||||||
|
@schemeblock[
|
||||||
|
@code:comment{post-title : post -> string?}
|
||||||
|
@code:comment{Queries for the title}
|
||||||
|
(define (post-title a-post)
|
||||||
|
(vector-ref
|
||||||
|
(second
|
||||||
|
(sqlite:select
|
||||||
|
(blog-db (post-blog a-post))
|
||||||
|
(format
|
||||||
|
"SELECT title FROM posts WHERE id = '~a'"
|
||||||
|
(post-id a-post))))
|
||||||
|
0))
|
||||||
|
]
|
||||||
|
|
||||||
|
@bold{Exercise.} Write the definition of @scheme[post-body].
|
||||||
|
|
||||||
|
@bold{Exercise.} Write the definition of @scheme[post-comments].
|
||||||
|
(Hint: Use @scheme[blog-posts] as a template, not @scheme[post-title].)
|
||||||
|
|
||||||
|
@centerline{------------}
|
||||||
|
|
||||||
|
The only change that we need to make to the application is to require the new model. The interface is exactly the same!
|
||||||
|
|
||||||
|
@centerline{------------}
|
||||||
|
|
||||||
|
Our model is now:
|
||||||
|
|
||||||
|
@external-file["model-3.ss"]
|
||||||
|
|
||||||
|
And our application is:
|
||||||
|
|
||||||
|
@schememod[
|
||||||
|
web-server/insta
|
||||||
|
|
||||||
|
(require "model-3.ss")
|
||||||
|
|
||||||
|
....
|
||||||
|
]
|
||||||
|
|
||||||
@section{Leaving DrScheme}
|
@section{Leaving DrScheme}
|
||||||
|
|
||||||
|
@ -991,6 +1163,6 @@ refer to the PLT Web Server Reference Manual for details.
|
||||||
|
|
||||||
@section{Moving Forward}
|
@section{Moving Forward}
|
||||||
|
|
||||||
As you move forward on your own applications, you may find many useful packages on PLaneT. There are interfaces to
|
As you move forward on your own applications, you may find many useful packages on PLaneT. There are interfaces to other
|
||||||
databases. Many tools for generating HTML, XML, and Javascript output. Etc. There is also an active community of
|
databases. Many tools for generating HTML, XML, and Javascript output. Etc. There is also an active community of
|
||||||
users on the @scheme[plt-scheme] mailing list. We welcome new users!
|
users on the @scheme[plt-scheme] mailing list. We welcome new users!
|
Loading…
Reference in New Issue
Block a user