Snooze applications interact with databases via scheme/class object that implement the interface. Each object establishes connections to a single database.
Snooze objects guarantee thread safety: multiple threads can connect concurrently to the same object without running into problems. This means that each object can actually maintain more than one database connection concurrently, although only one connection is ever visible in any given thread. This is illustrated by the following code sample:
|( ( untyped/snooze:2/snooze)|
|; Interface to the SQLite database in "mydata.db":|
|( mydata ( ( ( "mydata.db"))))|
|; (persistent-struct (U string #f))|
|; -> void|
|; Connects to the database and inserts a record:|
|( mydata (make-person "Dave")))))|
|; These threads won't interfere with one another:|
|( database) →|
|database : database<%>|
Creates a Snooze object to talk to the specified database. Snooze objects implement the interface, which contains methods for connecting to, querying, updating and maintaining the database.
(send a-snooze call-with-connection thunk) → thunk : ( )
Establishes a connection to the database and maintains it for the dynamic extent of thunk. The connection is closed when control is transferred outside of thunk via a continuation jump, an exception or a graceful return. The connection is re-established when control passes into thunk via a continuation jump.
Only one connection may be opened at a time in each thread. Child threads do not inherit their parents’ connections. is raised if a connection is already open when control is transferred into thunk.
(send a-snooze current-connection) → (U #f)
Returns the current connection, or #f if no connection is established.
The remaining methods require a connection to be open when they are called: is raised in all cases if this is not the case.
(send a-snooze find-all query) → ( result) query :
retrieves a list of all matching results from the database. conn is an optional connection: if omitted, the default connection from call-with-database is used.
query is a select statement, as returned by . It determines both the data retrieved and the type of each result.
(send a-snooze find-one query) → (U result #f) query :
Similar to , but only returns the first result found. If no results are found, returns #f instead.
(send a-snooze g:find query) → ( result) query :
Similar to , but returns a generator of results. This is the most general query mechanism offered by Snooze: generators allow you to manipulate results one at a time in a functional manner, without wasting lots of memory on intermediate lists.
(send a-snooze save! struct) → struct :
Inserts or updates the database record for the supplied struct:
If struct has an id of #f, assumes that no corresponding database record exists. It sets the revision field to 0 and uses an SQL "INSERT" statement to insert a new database record. Finally, sets the id to the primary key of the record and returns the mutated struct.
If struct already has an integer id when is called, the behaviour is different. First, checks the database to make sure the stored revision number matches the revision number in struct. It then increments the revision and uses an SQL "UPDATE" statement to update the database record with the new information. Finally, returns the mutated struct.
raises if a revision number check fails. This normally indicates that struct has been concurrently loaded and saved by another thread.
(send a-snooze delete! struct) → struct :
Deletes the database record for struct and sets its id and revision to #f. Returns the mutated struct to allow the programmer to chain the call with calls to other procedures.
raises if no database record exists, and if the revision in struct does not match the revision number stored in the database.
(send a-snooze create-table entity) → entity :
Issues an SQL "CREATE TABLE" statement to create a database table for the supplied entity. Raises if the table already exists or cannot be created.
(send a-snooze drop-table entity) → entity :
Issues an SQL "DROP TABLE" statement to delete the database table for the supplied entity. Does nothing if no table is present. Raises if the table exists and cannot be dropped.
(send a-snooze table-names) → ( )
Returns a list of the names of the tables defined in the database. If the database supports multiple namespaces, only tables in the standard or public namespace are returned.
(send a-snooze table-exists? table) → table : (U )
Checks to see if a table exists for the supplied entity or table name. Note that if the argument is a symbol it refers to a table name rather than an entity name.
The macro is provided for convenience, to convert the object oriented database interface above into a more Schemely procedural interface.
|( prefix-id snooze-object)|
Defines a procedure for each of the methods in , such that each procedure calls the matching method in the snooze-object. The optional prefix-id can be used to add a prefix to each identifier to avoid naming collisions. For example:
|( (sqlite:make-database ( "db1.sqlite"))))|
|( (sqlite:make-database ( "db2.sqlite"))))|
|( alt: db2-snooze)|
|; Connect to "db1.sqlite" and perform some operations:|
|; ... ))|
|; Connect to "db2.sqlite" and perform some operations:|
|; ... ))|
Expands into a set of forms for the functional Snooze interface defined by () or ( prefix-id).
When a persistent structure is first created, it has no corresponding record in the database. A record is saved (inserted or updated) with a call to the method or procedure, and deleted with a call to the method or procedure.
updates the database record for and sets its id and revision appropriately. It returns the mutated to allow the programmer to chain the call with calls to other procedures.
If has an id of #f, assumes that no corresponding database record exists. It sets the revision field to 0 and uses an SQL INSERT statement to insert a new database record. Finally, sets the id to the primary key of the record and returns the mutated .
If already has an integer id when is called, the behaviour is different. First, checks the database to make sure the stored revision number matches the revision number in . It then increments the revision and uses an SQL UPDATE statement to update the database record with the new information. Finally, returns the mutated .
raises if a revision number check fails. This is useful because it allows the programmer to detect and avoid concurrent updates.
Finally, uses an SQL DELETE statement to delete the corresponding record from the database. sets the id and revision of the struct to #f and then returns it.
The lifecycle of the id and revision fields is summarised in the code snippet below:
Note: The repeated calls to are only necessary to get Scribble to print the results of each statement: normal application programs should be able to all of this interaction with a single connection.
Extra note: The printed values in this example may not be correct. This is because of the way Scribble renders example blocks. A solution for this issue is being worked on.
|; Define a new persistent struct type:|
|; Create a DB table for this new type:|
|; Create a struct: initially it has no corresponding DB record:|
|> ( person (make-person "Dave"))|
#(struct:person #f #f "Noel")
|; Insert a DB record and set the struct's ID and revision:|
Could not insert database record for #(struct:person #f 0
Dave): SQLite Error: no such table: person
|; Update the record and increment the revision:|
|> (set-person-name! person "Noel")|
Could not insert database record for #(struct:person #f 0
Noel): SQLite Error: no such table: person
|; Deleted the record and set the ID and revision to #f:|
Cannot delete a struct that has not been saved to the
database: #(struct:person #f #f Noel)
|; Finally, delete the DB table to clean up:|