]> gitweb.factorcode.org Git - factor.git/commitdiff
clean up the db code some, more docs
authorDoug Coleman <doug.coleman@gmail.com>
Sat, 4 Oct 2008 01:19:20 +0000 (20:19 -0500)
committerDoug Coleman <doug.coleman@gmail.com>
Sat, 4 Oct 2008 01:19:20 +0000 (20:19 -0500)
basis/db/db-docs.factor
basis/db/db.factor
basis/db/sqlite/sqlite.factor
basis/db/tuples/tuples-docs.factor
basis/db/tuples/tuples.factor

index b0a01269aba92b11a3efef18b65556aac6f9fb9a..250e21fdaa2ccddd95c0bc9fbb23ed2b7a3d9de2 100644 (file)
@@ -9,7 +9,8 @@ HELP: db
 
 HELP: new-db
 { $values { "class" class } { "obj" object } }
-{ $description "Creates a new database object from a given class with caches for prepared statements. Does not actually connect to the database until " { $link db-open } " or " { $link with-db } " is called." } ;
+{ $description "Creates a new database object from a given class with caches for prepared statements. Does not actually connect to the database until " { $link db-open } " or " { $link with-db } " is called." }
+{ $notes "User-defined databases must call this constructor word instead of " { $link new } "." } ;
 
 HELP: db-open
 { $values { "db" db } { "db" db } }
@@ -19,6 +20,8 @@ HELP: db-close
 { $values { "handle" alien } }
 { $description "Closes a database using the handle provided. Use of the " { $link with-db } " combinator is preferred over manually opening and closing databases so that resources are not leaked." } ;
 
+{ db-open db-close with-db } related-words
+
 HELP: dispose-statements
 { $values { "assoc" assoc } }
 { $description "Disposes an associative list of statements." } ;
@@ -49,22 +52,28 @@ HELP: new-statement
 HELP: <simple-statement>
 { $values { "string" string } { "in" sequence } { "out" sequence }
     { "statement" statement } }
-{ $description "Makes a new simple statement object from the given parameters." } ;
+{ $description "Makes a new simple statement object from the given parameters.." }
+{ $warning "Using a simple statement can lead to SQL injection attacks in PostgreSQL. The Factor database implementation for SQLite only uses " { $link <prepared-statement> } " as the sole kind of statement; simple statements alias to prepared ones." } ;
 
 HELP: <prepared-statement>
 { $values { "string" string } { "in" sequence } { "out" sequence }
     { "statement" statement } }
-{ $description "Makes a new prepared statement object from the given parameters." } ;
+{ $description "Makes a new prepared statement object from the given parameters. A prepared statement's parameters will be escaped by the database backend to avoid SQL injection attacks. Prepared statements should be preferred over simple statements." } ;
 
 HELP: prepare-statement
 { $values { "statement" statement } }
 { $description "For databases which implement a method on this generic, it does some internal processing to ready the statement for execution." } ;
 
+HELP: low-level-bind
+{ $values
+     { "statement" statement } }
+{ $description "For use with prepared statements, methods on this word should bind the datatype in the SQL spec to its identifier in the SQL string. To name bound variables, SQLite uses identifiers in the form of " { $snippet ":name" } ", while PostgreSQL uses increasing numbers beginning with a dollar sign, e.g. " { $snippet "$1" } "." } ;
+
 HELP: query-results
 { $values { "query" object }
     { "result-set" result-set }
 }
-{ $description "Returns a " { $link result-set } " object representing the reults of a SQL query." } ;
+{ $description "Returns a " { $link result-set } " object representing the results of a SQL query. See " { $link "db-result-sets" } "." } ;
 
 HELP: #rows
 { $values { "result-set" result-set } { "n" integer } }
@@ -161,22 +170,20 @@ HELP: with-transaction
 { $description "" } ;
 
 ARTICLE: "db" "Database library"
+"Accessing a database:"
 { $subsection "db-custom-database-combinators" }
+"Higher-level database help:"
+{ $vocab-subsection "Database types" "db.types" }
+{ $vocab-subsection "High-level tuple/database integration" "db.tuples" }
+"Low-level database help:"
 { $subsection "db-protocol" }
 { $subsection "db-result-sets" }
 { $subsection "db-lowlevel-tutorial" }
-"Higher-level database:"
-{ $vocab-subsection "Database types" "db.types" }
-{ $vocab-subsection "High-level tuple/database integration" "db.tuples" }
-! { $subsection "db-tuples" }
-! { $subsection "db-tuples-protocol" }
-! { $subsection "db-tuples-tutorial" }
 "Supported database backends:"
 { $vocab-subsection "SQLite" "db.sqlite" }
 { $vocab-subsection "PostgreSQL" "db.postgresql" }
 "To add support for another database to Factor:"
-{ $subsection "db-porting-the-library" }
-;
+{ $subsection "db-porting-the-library" } ;
 
 ARTICLE: "db-random-access-result-set" "Random access result sets"
 "Random-access result sets do not have to be traversed in order. For instance, PostgreSQL's result set object can be accessed as a matrix with i,j coordinates."
@@ -234,7 +241,32 @@ ARTICLE: "db-protocol" "Low-level database protocol"
 
 ARTICLE: "db-lowlevel-tutorial" "Low-level database tutorial"
 "Although Factor makes integrating a database with its object system easy (see " { $vocab-link "db.tuples" } "), sometimes you may want to write SQL directly and get the results back as arrays of strings, for instance, when interfacing with a legacy database that doesn't easily map to " { $snippet "tuples" } "."
-;
+"Executing a SQL command:"
+{ $subsection sql-command }
+"Executing a query directly:"
+{ $subsection sql-query }
+"Here's an example usage where we'll make a book table, insert some objects, and query them." $nl
+"First, let's set up a custom combinator for using our database.  See " { $link "db-custom-database-combinators" } " for more details."
+{ $code <"
+USING: db.sqlite db io.files ;
+: with-book-db ( quot -- )
+    "book.db" temp-file <sqlite-db> swap with-db ;"> }
+"Now let's create the table manually:"
+{ $code <" "create table books
+    (id integer primary key, title text, author text, date_published timestamp,
+     edition integer, cover_price double, condition text)"
+    [ sql-command ] with-book-db" "> }
+"Time to insert some books:"
+{ $code <"
+"insert into books
+    (title, author, date_published, edition, cover_price, condition)
+    values('Factor for Sheeple', 'Mister Stacky Pants', date('now'), 1, 13.37, 'mint')"
+[ sql-command ] with-book-db"> }
+"Now let's select the book:"
+{ $code <"
+"select id, title, cover_price from books;" [ sql-query ] with-book-db "> }
+"Notice that the result of this query is a Factor array containing the database rows as arrays of strings. We would have to convert the " { $snippet "cover_price" } " from a string to a number in order to use it in a calculation." $nl
+"In conclusion, this method of accessing a database is supported, but it is fairly low-level and generally specific to a single database. The " { $vocab-link "db.tuples" } " vocabulary is a good alternative to writing SQL by hand." ;
 
 ARTICLE: "db-porting-the-library" "Porting the database library"
 "There are two layers to implement when porting the database library."
@@ -244,22 +276,24 @@ ARTICLE: "db-porting-the-library" "Porting the database library"
 ARTICLE: "db-custom-database-combinators" "Custom database combinators"
 "Every database library requires some effort on the programmer's part to initialize and open a database. SQLite uses files on your harddisk, so a simple pathname is all the setup required. With PostgreSQL, you log in to a networked server as a user on a specfic port." $nl
 
-"Make a " { $snippet "with-" } " combinator to open and close a database so that resources are not leaked."
+"Make a " { $snippet "with-" } " combinator to open and close a database so that resources are not leaked." $nl
+
+"SQLite example combinator:"
 { $code <"
 USING: db.sqlite db io.files ;
 : with-sqlite-db ( quot -- )
-    "my-database.db" temp-file <sqlite-db> rot with-db ;"> } 
+    "my-database.db" temp-file <sqlite-db> swap with-db ;"> } 
 
+"PostgreSQL example combinator:"
 { $code <" USING: db.postgresql db ;
 : with-postgresql-db ( quot -- )
     <postgresql-db>
         "localhost" >>host
+        5432 >>port
         "erg" >>username
         "secrets?" >>password
         "factor-test" >>database
     swap with-db ;">
-}
-
-;
+} ;
 
 ABOUT: "db"
index 5b159d0ea19f4859426b587fbaeffb313680500d..bf23005bc21cb975f3871bc6ff1a2dcafb18e5b8 100644 (file)
@@ -111,22 +111,22 @@ M: object execute-statement* ( statement type -- )
     [ db-open db ] dip
     '[ db get [ drop @ ] with-disposal ] with-variable ; inline
 
+! Words for working with raw SQL statements
 : default-query ( query -- result-set )
     query-results [ [ sql-row ] query-map ] with-disposal ;
 
 : sql-query ( sql -- rows )
     f f <simple-statement> [ default-query ] with-disposal ;
 
+: (sql-command) ( string -- )
+    f f <simple-statement> [ execute-statement ] with-disposal ;
+
 : sql-command ( sql -- )
-    dup string? [
-        f f <simple-statement> [ execute-statement ] with-disposal
-    ] [
-        ! [
-            [ sql-command ] each
-        ! ] with-transaction
-    ] if ;
+    dup string? [ (sql-command) ] [ [ (sql-command) ] each ] if ;
 
+! Transactions
 SYMBOL: in-transaction
+
 HOOK: begin-transaction db ( -- )
 HOOK: commit-transaction db ( -- )
 HOOK: rollback-transaction db ( -- )
index dfe4fdf47523d968b8a66bd2845efb0b13b3d8c1..8580b9012ca537d8a60198fd9b65c2fbc281a6bb 100644 (file)
@@ -49,8 +49,8 @@ M: sqlite-result-set dispose ( result-set -- )
     handle>> [ sqlite3_reset drop ] [ sqlite3_clear_bindings drop ] bi ;
 
 M: sqlite-statement low-level-bind ( statement -- )
-    [ bind-params>> ] [ handle>> ] bi
-    [ swap [ key>> ] [ value>> ] [ type>> ] tri sqlite-bind-type ] curry each ;
+    [ handle>> ] [ bind-params>> ] bi
+    [ [ key>> ] [ value>> ] [ type>> ] tri sqlite-bind-type ] with each ;
 
 M: sqlite-statement bind-statement* ( statement -- )
     sqlite-maybe-prepare
index 497cc2e8c545e4921e9feb91077a742bcfd23b1a..8b1c8cbb496eda32f7bc92ad10efddfe21c21f11 100644 (file)
@@ -1,9 +1,46 @@
 ! Copyright (C) 2008 Doug Coleman.
 ! See http://factorcode.org/license.txt for BSD license.
 USING: classes help.markup help.syntax io.streams.string kernel
-quotations sequences strings multiline math db.types ;
+quotations sequences strings multiline math db.types db ;
 IN: db.tuples
 
+HELP: <count-statement>
+{ $values
+     { "query" query }
+     { "statement" statement } }
+{ $description "A database-specific hook for generating the SQL for a count statement." } ;
+
+HELP: <delete-tuples-statement>
+{ $values
+     { "tuple" tuple } { "class" class }
+     { "object" object } }
+{ $description "A database-specific hook for generating the SQL for an delete statement." } ;
+
+HELP: <insert-db-assigned-statement>
+{ $values
+     { "class" class }
+     { "object" object } }
+{ $description "A database-specific hook for generating the SQL for an insert statement with a database-assigned primary key." } ;
+
+HELP: <insert-user-assigned-statement>
+{ $values
+     { "class" class }
+     { "object" object } }
+{ $description "A database-specific hook for generating the SQL for an insert statement with a user-assigned primary key." } ;
+
+HELP: <select-by-slots-statement>
+{ $values
+     { "tuple" tuple } { "class" class }
+     { "tuple" tuple } }
+{ $description "A database-specific hook for generating the SQL for a select statement." } ;
+
+HELP: <update-tuple-statement>
+{ $values
+     { "class" class }
+     { "object" object } }
+{ $description "A database-specific hook for generating the SQL for an update statement." } ;
+
+
 HELP: define-persistent
 { $values
      { "class" class } { "table" string } { "columns" "an array of slot specifiers" } }
@@ -128,7 +165,21 @@ ARTICLE: "db-tuples-words" "High-level tuple/database words"
 { $subsection count-tuples } ;
 
 ARTICLE: "db-tuples-protocol" "Tuple database protocol"
-;
+"Creating a table:"
+{ $subsection create-sql-statement }
+"Dropping a table:"
+{ $subsection drop-sql-statement }
+"Inserting a tuple:"
+{ $subsection <insert-db-assigned-statement> }
+{ $subsection <insert-user-assigned-statement> }
+"Updating a tuple:"
+{ $subsection <update-tuple-statement> }
+"Deleting tuples:"
+{ $subsection <delete-tuples-statement> }
+"Selecting tuples:"
+{ $subsection <select-by-slots-statement> }
+"Counting tuples:"
+{ $subsection <count-statement> } ;
 
 ARTICLE: "db-tuples-tutorial" "Tuple database tutorial"
 "Let's make a tuple and store it in a database. To follow along, click on each code example and run it in the listener.  If you forget to run an example, just start at the top and run them all again in order." $nl
index 20d7ac4f843c5cfad2e1e6124913f9f5ced72091..7a5c9e41e68b87d2c8e28cc9f7df0b16f143ed89 100644 (file)
@@ -6,8 +6,6 @@ math.parser io prettyprint db.types continuations
 destructors mirrors sets db.types ;
 IN: db.tuples
 
-<PRIVATE
-
 HOOK: create-sql-statement db ( class -- object )
 HOOK: drop-sql-statement db ( class -- object )
 
@@ -18,10 +16,12 @@ HOOK: <delete-tuples-statement> db ( tuple class -- object )
 HOOK: <select-by-slots-statement> db ( tuple class -- tuple )
 HOOK: <count-statement> db ( query -- statement )
 HOOK: query>statement db ( query -- statement )
-
 HOOK: insert-tuple-set-key db ( tuple statement -- )
 
+<PRIVATE
+
 SYMBOL: sql-counter
+
 : next-sql-counter ( -- str )
     sql-counter [ inc ] [ get ] bi number>string ;
 
@@ -68,7 +68,6 @@ GENERIC: eval-generator ( singleton -- object )
 
 PRIVATE>
 
-
 ! High level
 ERROR: no-slots-named class seq ;
 : check-columns ( class columns -- )