1 ! Copyright (C) 2005 Chris Double.
3 ! A tuple that is persistent has its delegate set as 'persistent'.
4 ! 'persistent' holds the numeric rowid for that tuple in its table.
6 USING: io kernel kernel-internals sequences namespaces
7 hashtables sqlite errors math words generic ;
9 ! Each slot in a tuple that is storable in the database has
10 ! an instance of a db-field object the gives the name of the
11 ! database table and slot number in the tuple object of that field.
12 TUPLE: db-field name bind-name slot type ;
14 ! The mapping tuple holds information on how the slots of
15 ! a tuple are mapped to the fields of a sqlite database.
16 TUPLE: mapping tuple table fields one-to-one one-to-many ;
18 : sanitize-conversions ( -- alist )
24 : sanitize ( string -- string )
25 #! Convert a string so it can be used as a table or field name.
27 [ dup sanitize-conversions hash [ % ] [ , ] ?if ] each
30 : tuple-fields ( class -- seq )
31 #! Given a tuple class return a list of the fields
32 #! within that tuple. Ignores the delegate field.
33 [ word-name length 1+ ] keep
34 "slots" word-prop 1 tail [ ( name-len { slot getter setter } )
35 [ third word-name swap tail sanitize dup ":" swap append ] keep
41 : default-mapping ( class -- mapping )
42 #! Given a tuple class, create a default mappings object. It assumes
43 #! there are no one-to-one or one-to-many relationships.
44 dup [ word-name sanitize ] keep tuple-fields f f <mapping> ;
46 ! The mappings variable holds a hashtable mapping the tuple symbol
47 ! to the mapping object, describing how that tuple is stored
51 : init-mappings ( -- )
53 H{ } mappings set-global ;
55 : get-mappings ( -- hashtable )
58 : set-mapping ( mapping -- )
59 #! Store a database mapping so that the persistence system
60 #! knows how to store instances of the relevant tuple in the database.
61 dup mapping-tuple get-mappings set-hash ;
63 : get-mapping ( class -- mapping )
64 #! Return the database mapping for the given tuple class.
67 ! The 'persistent' tuple will be set to the delegate of any tuple
68 ! instance stored in the database. It contains the database key
69 ! of the row in the database table for the instance or 'f' if it has
70 ! not yet been stored in the database. It also contains the 'mapping'
71 ! object used to translate the fields of the tuple to the database fields.
72 TUPLE: persistent mapping key ;
73 C: persistent ( tuple -- persistent )
74 >r class get-mapping r>
75 [ set-persistent-mapping ] keep ;
77 : make-persistent ( tuple -- tuple )
78 #! Convert the tuple into something that can be stored
79 #! into a database by setting its delegate to 'persistent'.
81 [ set-delegate ] keep ;
84 : comma-fields ( mapping quot -- string )
85 #! Given a mapping, call quot on each field in
86 #! the mapping. The contents of quot should call ',' or '%'
87 #! to generate output. The output of each quot call
88 #! seperated by commas is returned as a string. 'quot' should be
89 #! stack effect ( field -- ).
92 ] map-with "," join ; inline
95 M: mapping create-sql ( mapping -- string )
96 #! Return the SQL used to create a table for storing this type of tuple.
98 "create table " % dup mapping-table %
100 [ dup db-field-name % " " % db-field-type % ] comma-fields %
105 M: mapping drop-sql ( mapping -- string )
106 #! Return the SQL used to drop the table for storing this type of tuple.
108 "drop table " % mapping-table % ";" %
112 M: mapping insert-sql ( mapping -- string )
113 #! Return the SQL used to insert a tuple into a table
115 "insert into " % dup mapping-table %
117 [ db-field-bind-name % ] comma-fields %
122 M: mapping delete-sql ( mapping -- string )
123 #! Return the SQL used to delete a tuple from a table
125 "delete from " % mapping-table %
126 " where ROWID=:rowid;" %
130 M: mapping update-sql ( mapping -- string )
131 #! Return the SQL used to update the tuple
133 "update " % dup mapping-table %
135 [ dup db-field-name % "=" % db-field-bind-name % ] comma-fields %
136 " where ROWID=:rowid;" %
140 M: mapping select-sql ( tuple mapping -- select )
141 #! Return the SQL used to select a series of tuples from the database. It
142 #! will select based on only the filled in fields of the tuple (ie. all non-f).
144 "select ROWID,* from " % dup mapping-table %
145 mapping-fields [ ! tuple field
146 swap over db-field-slot slot ! field value
148 [ dup db-field-name % "=" % db-field-bind-name % ] "" make
152 ] map-with [ ] subset dup length 0 > [
161 : execute-update-sql ( db string -- )
162 #! Execute the SQL, which should contain a database update
163 #! statement (update, insert, create, etc). Ignore the result.
164 sqlite-prepare dup [ drop ] sqlite-each sqlite-finalize ;
166 : create-tuple-table ( db class -- )
167 #! Create the table for the tuple class.
168 get-mapping create-sql execute-update-sql ;
170 : drop-tuple-table ( db class -- )
171 #! Create the table for the tuple class.
172 get-mapping drop-sql execute-update-sql ;
174 : bind-for-insert ( statement tuple -- )
175 #! Bind the fields in the tuple to the fields in the
176 #! prepared insert statement.
177 dup class get-mapping mapping-fields [ ! statement tuple field
178 [ db-field-slot slot ] keep ! statement value field
179 db-field-bind-name swap ! statement name value
180 >r dupd r> sqlite-bind-text-by-name
183 : bind-for-select ( statement tuple -- )
184 #! Bind the fields in the tuple to the fields in the
185 #! prepared select statement.
186 dup class get-mapping mapping-fields [ ! statement tuple field
187 [ db-field-slot slot ] keep ! statement value field
189 db-field-bind-name swap ! statement name value
190 >r dupd r> sqlite-bind-text-by-name
196 : bind-for-update ( statement tuple -- )
197 #! Bind the fields in the tuple to the fields in the
198 #! prepared update statement.
200 >r ":rowid" r> persistent-key sqlite-bind-text-by-name ;
202 : bind-for-delete ( statement tuple -- )
203 #! Bind the fields in the tuple to the fields in the
204 #! prepared delete statement.
205 >r ":rowid" r> persistent-key sqlite-bind-text-by-name ;
207 : (insert-tuple) ( db tuple -- )
208 #! Insert this tuple instance into the database. Note that
209 #! it inserts only this instance, and not any one-to-one or
210 #! one-to-many fields.
211 dup class get-mapping insert-sql ! db tuple sql
212 swapd sqlite-prepare swap ! statement tuple
213 dupd bind-for-insert ! statement
214 dup [ drop ] sqlite-each
217 : insert-tuple ( db tuple -- )
218 #! Insert this tuple instance into the database and
219 #! update the rowid of the insert in the tuple.
220 [ (insert-tuple) ] 2keep
221 >r sqlite-last-insert-rowid number>string r> make-persistent set-persistent-key ;
223 : update-tuple ( db tuple -- )
224 #! Update this tuple instance in the database. The tuple should have
225 #! a delegate of 'persistent' with the key field set.
226 dup class get-mapping update-sql ! db tuple sql
227 swapd sqlite-prepare swap ! statement tuple
228 dupd bind-for-update ! statement
229 dup [ drop ] sqlite-each
232 : save-tuple ( db tuple -- )
233 #! Insert or Update the tuple instance depending on whether it
234 #! has a persistent delegate.
235 dup delegate [ update-tuple ] [ insert-tuple ] if ;
237 : delete-tuple ( db tuple -- )
238 #! Delete this tuple instance from the database. The tuple should have
239 #! a delegate of 'persistent' with the key field set.
240 dup class get-mapping delete-sql ! db tuple sql
241 swapd sqlite-prepare swap ! statement tuple
242 dupd bind-for-delete ! statement
243 dup [ drop ] sqlite-each
246 : restore-tuple ( statement tuple -- tuple )
247 #! Using 'tuple' as a template, clone it and
248 #! return the clone with fields set to the values from the
250 clone dup class get-mapping mapping-fields 1 swap
251 [ ! statement tuple index field )
252 over 1+ >r ! statement tuple index field r: index+1
253 db-field-slot >r ! statement tuple index r: index+1 slot
254 pick swap column-text ! statement tuple value r: index+1 slot
255 over r> set-slot r> ! statement tuple index+1
256 ] each ! statement tuple index
257 drop make-persistent swap 0 column-text swap [ set-persistent-key ] keep ;
259 : find-tuples ( db tuple -- seq )
260 #! Return a sequence of all tuples in the database that
261 #! match the tuple provided as a template. All fields in the
262 #! tuple must match the entries in the database, except for
264 dup class get-mapping dupd select-sql ! db tuple sql
265 swapd sqlite-prepare swap ! statement tuple
266 2dup bind-for-select ! statement tuple
268 over [ ! tuple statement
271 ] [ ] make nip ! statement tuple accum
272 swap sqlite-finalize ;
275 get-mappings [ init-mappings ] unless