271 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Factor
		
	
	
		
		
			
		
	
	
			271 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Factor
		
	
	
| 
								 | 
							
								! Copyright (C) 2005 Chris Double.
							 | 
						||
| 
								 | 
							
								!
							 | 
						||
| 
								 | 
							
								! A tuple that is persistent has its delegate set as 'persistent'.
							 | 
						||
| 
								 | 
							
								! 'persistent' holds the numeric rowid for that tuple in its table.
							 | 
						||
| 
								 | 
							
								IN: sqlite.tuple-db
							 | 
						||
| 
								 | 
							
								USING: io kernel sequences namespaces slots classes slots.private
							 | 
						||
| 
								 | 
							
								assocs math words generic sqlite math.parser ;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								! Each slot in a tuple that is storable in the database has
							 | 
						||
| 
								 | 
							
								! an instance of a db-field object the gives the name of the 
							 | 
						||
| 
								 | 
							
								! database table and slot number in the tuple object of that field.
							 | 
						||
| 
								 | 
							
								TUPLE: db-field name bind-name slot type ;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								C: <db-field> db-field
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								! The mapping tuple holds information on how the slots of
							 | 
						||
| 
								 | 
							
								! a tuple are mapped to the fields of a sqlite database. 
							 | 
						||
| 
								 | 
							
								TUPLE: mapping tuple table fields one-to-one one-to-many   ;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								C: <mapping> mapping
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								: sanitize ( string -- string ) 
							 | 
						||
| 
								 | 
							
								    #! Convert a string so it can be used as a table or field name.
							 | 
						||
| 
								 | 
							
								    clone
							 | 
						||
| 
								 | 
							
								    H{ { CHAR: - CHAR: _ } { CHAR: ? CHAR: p } }
							 | 
						||
| 
								 | 
							
								    over substitute ;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								: tuple-fields ( class -- seq )
							 | 
						||
| 
								 | 
							
								  #! Given a tuple class return a list of the fields
							 | 
						||
| 
								 | 
							
								  #! within that tuple. Ignores the delegate field.
							 | 
						||
| 
								 | 
							
								  "slots" word-prop 1 tail [
							 | 
						||
| 
								 | 
							
								    [ slot-spec-name sanitize dup ":" swap append ] keep
							 | 
						||
| 
								 | 
							
								    slot-spec-offset
							 | 
						||
| 
								 | 
							
								    "text"
							 | 
						||
| 
								 | 
							
								    <db-field>
							 | 
						||
| 
								 | 
							
								  ] map ;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								: default-mapping ( class -- mapping )  
							 | 
						||
| 
								 | 
							
								  #! Given a tuple class, create a default mappings object. It assumes
							 | 
						||
| 
								 | 
							
								  #! there are no one-to-one or one-to-many relationships.
							 | 
						||
| 
								 | 
							
								  dup [ word-name sanitize ] keep tuple-fields f f <mapping> ;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								! The mappings variable holds a hashtable mapping the tuple symbol
							 | 
						||
| 
								 | 
							
								! to the mapping object, describing how that tuple is stored
							 | 
						||
| 
								 | 
							
								! in the database.
							 | 
						||
| 
								 | 
							
								SYMBOL: mappings
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								: init-mappings ( -- )
							 | 
						||
| 
								 | 
							
								  H{ } mappings set-global ;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								: get-mappings ( -- hashtable )
							 | 
						||
| 
								 | 
							
								  mappings get-global ;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								: set-mapping ( mapping -- )
							 | 
						||
| 
								 | 
							
								  #! Store a database mapping so that the persistence system 
							 | 
						||
| 
								 | 
							
								  #! knows how to store instances of the relevant tuple in the database.
							 | 
						||
| 
								 | 
							
								  dup mapping-tuple get-mappings set-at ;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								: get-mapping ( class -- mapping )
							 | 
						||
| 
								 | 
							
								  #! Return the database mapping for the given tuple class.
							 | 
						||
| 
								 | 
							
								  get-mappings at ;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								! The 'persistent' tuple will be set to the delegate of any tuple
							 | 
						||
| 
								 | 
							
								! instance stored in the database. It contains the database key
							 | 
						||
| 
								 | 
							
								! of the row in the database table for the instance or 'f' if it has
							 | 
						||
| 
								 | 
							
								! not yet been stored in the database. It also contains the 'mapping'
							 | 
						||
| 
								 | 
							
								! object used to translate the fields of the tuple to the database fields.
							 | 
						||
| 
								 | 
							
								TUPLE: persistent mapping key ;
							 | 
						||
| 
								 | 
							
								: <persistent> ( tuple -- persistent )
							 | 
						||
| 
								 | 
							
								  persistent construct-empty
							 | 
						||
| 
								 | 
							
								  >r class get-mapping r> 
							 | 
						||
| 
								 | 
							
								  [ set-persistent-mapping ] keep ;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								: make-persistent ( tuple -- tuple )
							 | 
						||
| 
								 | 
							
								  #! Convert the tuple into something that can be stored
							 | 
						||
| 
								 | 
							
								  #! into a database by setting its delegate to 'persistent'.
							 | 
						||
| 
								 | 
							
								  [ <persistent> ] keep 
							 | 
						||
| 
								 | 
							
								  [ set-delegate ] keep ;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								: comma-fields ( mapping quot -- string )
							 | 
						||
| 
								 | 
							
								  #! Given a mapping, call quot on each field in
							 | 
						||
| 
								 | 
							
								  #! the mapping. The contents of quot should call ',' or '%'
							 | 
						||
| 
								 | 
							
								  #! to generate output. The output of each quot call
							 | 
						||
| 
								 | 
							
								  #! seperated by commas is returned as a string. 'quot' should be
							 | 
						||
| 
								 | 
							
								  #! stack effect ( field -- ).
							 | 
						||
| 
								 | 
							
								  >r mapping-fields r> [ "" make ] curry map "," join ; inline
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								GENERIC: create-sql ( mapping -- string )
							 | 
						||
| 
								 | 
							
								M: mapping create-sql ( mapping -- string )
							 | 
						||
| 
								 | 
							
								  #! Return the SQL used to create a table for storing this type of tuple.
							 | 
						||
| 
								 | 
							
								  [
							 | 
						||
| 
								 | 
							
								    "create table " % dup mapping-table % 
							 | 
						||
| 
								 | 
							
								    " (" % 
							 | 
						||
| 
								 | 
							
								    [ dup db-field-name % " " % db-field-type % ] comma-fields %
							 | 
						||
| 
								 | 
							
								    ");" %
							 | 
						||
| 
								 | 
							
								  ] "" make ;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								GENERIC: drop-sql ( mapping -- string )
							 | 
						||
| 
								 | 
							
								M: mapping drop-sql ( mapping -- string )
							 | 
						||
| 
								 | 
							
								  #! Return the SQL used to drop the table for storing this type of tuple.
							 | 
						||
| 
								 | 
							
								  [
							 | 
						||
| 
								 | 
							
								    "drop table " % mapping-table % ";" %
							 | 
						||
| 
								 | 
							
								  ] "" make ;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								GENERIC: insert-sql ( mapping -- string )
							 | 
						||
| 
								 | 
							
								M: mapping insert-sql ( mapping -- string )
							 | 
						||
| 
								 | 
							
								  #! Return the SQL used to insert a tuple into a table
							 | 
						||
| 
								 | 
							
								  [
							 | 
						||
| 
								 | 
							
								    "insert into " % dup mapping-table %
							 | 
						||
| 
								 | 
							
								    " values(" %
							 | 
						||
| 
								 | 
							
								    [ db-field-bind-name % ] comma-fields %
							 | 
						||
| 
								 | 
							
								    ");" %
							 | 
						||
| 
								 | 
							
								  ] "" make ;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								GENERIC: delete-sql ( mapping -- string )
							 | 
						||
| 
								 | 
							
								M: mapping delete-sql ( mapping -- string )
							 | 
						||
| 
								 | 
							
								  #! Return the SQL used to delete a tuple from a table
							 | 
						||
| 
								 | 
							
								  [
							 | 
						||
| 
								 | 
							
								    "delete from " % mapping-table %
							 | 
						||
| 
								 | 
							
								    " where ROWID=:rowid;" % 
							 | 
						||
| 
								 | 
							
								  ] "" make ;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								GENERIC: update-sql ( mapping -- string )
							 | 
						||
| 
								 | 
							
								M: mapping update-sql ( mapping -- string )
							 | 
						||
| 
								 | 
							
								  #! Return the SQL used to update the tuple
							 | 
						||
| 
								 | 
							
								  [
							 | 
						||
| 
								 | 
							
								    "update " % dup mapping-table %
							 | 
						||
| 
								 | 
							
								    " set " %
							 | 
						||
| 
								 | 
							
								    [ dup db-field-name % "=" % db-field-bind-name % ] comma-fields %
							 | 
						||
| 
								 | 
							
								    " where ROWID=:rowid;" %
							 | 
						||
| 
								 | 
							
								  ] "" make ;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								GENERIC: select-sql ( tuple mapping -- select )
							 | 
						||
| 
								 | 
							
								M: mapping select-sql ( tuple mapping -- select )
							 | 
						||
| 
								 | 
							
								  #! Return the SQL used to select a series of tuples from the database. It
							 | 
						||
| 
								 | 
							
								  #! will select based on only the filled in fields of the tuple (ie. all non-f).
							 | 
						||
| 
								 | 
							
								  [
							 | 
						||
| 
								 | 
							
								    "select ROWID,* from " % dup mapping-table %
							 | 
						||
| 
								 | 
							
								    mapping-fields [ ! tuple field
							 | 
						||
| 
								 | 
							
								      swap over db-field-slot slot ! field value
							 | 
						||
| 
								 | 
							
								      [
							 | 
						||
| 
								 | 
							
								        [ dup db-field-name % "=" % db-field-bind-name % ] "" make        
							 | 
						||
| 
								 | 
							
								      ] [
							 | 
						||
| 
								 | 
							
								        drop f
							 | 
						||
| 
								 | 
							
								      ] if
							 | 
						||
| 
								 | 
							
								    ] curry* map [ ] subset dup length 0 > [
							 | 
						||
| 
								 | 
							
								      " where " % 
							 | 
						||
| 
								 | 
							
								      " and " join % 
							 | 
						||
| 
								 | 
							
								    ] [
							 | 
						||
| 
								 | 
							
								      drop
							 | 
						||
| 
								 | 
							
								    ] if
							 | 
						||
| 
								 | 
							
								    ";" %
							 | 
						||
| 
								 | 
							
								  ] "" make ;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								: execute-update-sql ( db string -- )
							 | 
						||
| 
								 | 
							
								  #! Execute the SQL, which should contain a database update
							 | 
						||
| 
								 | 
							
								  #! statement (update, insert, create, etc). Ignore the result.
							 | 
						||
| 
								 | 
							
								  sqlite-prepare dup [ drop ] sqlite-each sqlite-finalize ;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								: create-tuple-table ( db class -- )
							 | 
						||
| 
								 | 
							
								  #! Create the table for the tuple class.
							 | 
						||
| 
								 | 
							
								  get-mapping create-sql execute-update-sql ;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								: drop-tuple-table ( db class -- )
							 | 
						||
| 
								 | 
							
								  #! Create the table for the tuple class.
							 | 
						||
| 
								 | 
							
								  get-mapping drop-sql execute-update-sql ;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								: bind-for-insert ( statement tuple -- )
							 | 
						||
| 
								 | 
							
								  #! Bind the fields in the tuple to the fields in the 
							 | 
						||
| 
								 | 
							
								  #! prepared insert statement.
							 | 
						||
| 
								 | 
							
								  dup class get-mapping mapping-fields [ ! statement tuple field
							 | 
						||
| 
								 | 
							
								    [ db-field-slot slot ] keep ! statement value field
							 | 
						||
| 
								 | 
							
								    db-field-bind-name swap ! statement name value
							 | 
						||
| 
								 | 
							
								    >r dupd r> sqlite-bind-text-by-name     
							 | 
						||
| 
								 | 
							
								  ] curry* each drop ;  
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								: bind-for-select ( statement tuple -- )
							 | 
						||
| 
								 | 
							
								  #! Bind the fields in the tuple to the fields in the 
							 | 
						||
| 
								 | 
							
								  #! prepared select statement.
							 | 
						||
| 
								 | 
							
								  dup class get-mapping mapping-fields [ ! statement tuple field
							 | 
						||
| 
								 | 
							
								    [ db-field-slot slot ] keep ! statement value field
							 | 
						||
| 
								 | 
							
								    over [
							 | 
						||
| 
								 | 
							
								      db-field-bind-name swap ! statement name value
							 | 
						||
| 
								 | 
							
								      >r dupd r> sqlite-bind-text-by-name     
							 | 
						||
| 
								 | 
							
								    ] [ 
							 | 
						||
| 
								 | 
							
								      2drop 
							 | 
						||
| 
								 | 
							
								    ] if
							 | 
						||
| 
								 | 
							
								  ] curry* each drop ;  
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								: bind-for-update ( statement tuple -- )
							 | 
						||
| 
								 | 
							
								  #! Bind the fields in the tuple to the fields in the 
							 | 
						||
| 
								 | 
							
								  #! prepared update statement.
							 | 
						||
| 
								 | 
							
								  2dup bind-for-insert
							 | 
						||
| 
								 | 
							
								  >r ":rowid" r> persistent-key sqlite-bind-text-by-name ;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								: bind-for-delete ( statement tuple -- )
							 | 
						||
| 
								 | 
							
								  #! Bind the fields in the tuple to the fields in the 
							 | 
						||
| 
								 | 
							
								  #! prepared delete statement.
							 | 
						||
| 
								 | 
							
								  >r ":rowid" r> persistent-key sqlite-bind-text-by-name ;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								: (insert-tuple) ( db tuple -- )
							 | 
						||
| 
								 | 
							
								  #! Insert this tuple instance into the database. Note that
							 | 
						||
| 
								 | 
							
								  #! it inserts only this instance, and not any one-to-one or
							 | 
						||
| 
								 | 
							
								  #! one-to-many fields.
							 | 
						||
| 
								 | 
							
								  dup class get-mapping insert-sql ! db tuple sql
							 | 
						||
| 
								 | 
							
								  swapd sqlite-prepare swap ! statement tuple
							 | 
						||
| 
								 | 
							
								  dupd bind-for-insert ! statement
							 | 
						||
| 
								 | 
							
								  dup [ drop ] sqlite-each
							 | 
						||
| 
								 | 
							
								  sqlite-finalize ;
							 | 
						||
| 
								 | 
							
								  
							 | 
						||
| 
								 | 
							
								: insert-tuple ( db tuple -- )
							 | 
						||
| 
								 | 
							
								  #! Insert this tuple instance into the database and
							 | 
						||
| 
								 | 
							
								  #! update the rowid of the insert in the tuple.
							 | 
						||
| 
								 | 
							
								  [ (insert-tuple) ] 2keep
							 | 
						||
| 
								 | 
							
								  >r sqlite-last-insert-rowid number>string r> make-persistent set-persistent-key ;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								: update-tuple ( db tuple -- )
							 | 
						||
| 
								 | 
							
								  #! Update this tuple instance in the database. The tuple should have
							 | 
						||
| 
								 | 
							
								  #! a delegate of 'persistent' with the key field set.
							 | 
						||
| 
								 | 
							
								  dup class get-mapping update-sql ! db tuple sql
							 | 
						||
| 
								 | 
							
								  swapd sqlite-prepare swap ! statement tuple
							 | 
						||
| 
								 | 
							
								  dupd bind-for-update ! statement
							 | 
						||
| 
								 | 
							
								  dup [ drop ] sqlite-each
							 | 
						||
| 
								 | 
							
								  sqlite-finalize ;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								: save-tuple ( db tuple -- )
							 | 
						||
| 
								 | 
							
								  #! Insert or Update the tuple instance depending on whether it
							 | 
						||
| 
								 | 
							
								  #! has a persistent delegate.
							 | 
						||
| 
								 | 
							
								  dup delegate [ update-tuple ] [ insert-tuple ] if ;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								: delete-tuple ( db tuple -- )
							 | 
						||
| 
								 | 
							
								  #! Delete this tuple instance from the database. The tuple should have
							 | 
						||
| 
								 | 
							
								  #! a delegate of 'persistent' with the key field set.
							 | 
						||
| 
								 | 
							
								  dup class get-mapping delete-sql ! db tuple sql
							 | 
						||
| 
								 | 
							
								  swapd sqlite-prepare swap ! statement tuple
							 | 
						||
| 
								 | 
							
								  dupd bind-for-delete ! statement
							 | 
						||
| 
								 | 
							
								  dup [ drop ] sqlite-each
							 | 
						||
| 
								 | 
							
								  sqlite-finalize ;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								: restore-tuple ( statement tuple -- tuple )
							 | 
						||
| 
								 | 
							
								  #! Using 'tuple' as a template, clone it and 
							 | 
						||
| 
								 | 
							
								  #! return the clone with fields set to the values from the
							 | 
						||
| 
								 | 
							
								  #! database.
							 | 
						||
| 
								 | 
							
								  clone dup class get-mapping mapping-fields 1 swap 
							 | 
						||
| 
								 | 
							
								  [ ! statement tuple index field )
							 | 
						||
| 
								 | 
							
								    over 1+ >r ! statement tuple index field r: index+1
							 | 
						||
| 
								 | 
							
								    db-field-slot >r ! statement tuple index r: index+1 slot
							 | 
						||
| 
								 | 
							
								    pick swap column-text ! statement tuple value r: index+1 slot
							 | 
						||
| 
								 | 
							
								    over r> set-slot r> ! statement tuple index+1
							 | 
						||
| 
								 | 
							
								  ] each ! statement tuple index
							 | 
						||
| 
								 | 
							
								  drop make-persistent swap 0 column-text swap [ set-persistent-key ] keep ; 
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								: find-tuples ( db tuple -- seq )
							 | 
						||
| 
								 | 
							
								  #! Return a sequence of all tuples in the database that
							 | 
						||
| 
								 | 
							
								  #! match the tuple provided as a template. All fields in the
							 | 
						||
| 
								 | 
							
								  #! tuple must match the entries in the database, except for 
							 | 
						||
| 
								 | 
							
								  #! those set to 'f'. 
							 | 
						||
| 
								 | 
							
								  dup class get-mapping dupd select-sql ! db tuple sql
							 | 
						||
| 
								 | 
							
								  swapd sqlite-prepare swap ! statement tuple
							 | 
						||
| 
								 | 
							
								  2dup bind-for-select ! statement tuple
							 | 
						||
| 
								 | 
							
								  [
							 | 
						||
| 
								 | 
							
								    over [ ! tuple statement
							 | 
						||
| 
								 | 
							
								      over restore-tuple ,
							 | 
						||
| 
								 | 
							
								    ] sqlite-each 
							 | 
						||
| 
								 | 
							
								  ] { } make nip ! statement tuple accum
							 | 
						||
| 
								 | 
							
								  swap sqlite-finalize ;
							 | 
						||
| 
								 | 
							
								  
							 | 
						||
| 
								 | 
							
								 
							 | 
						||
| 
								 | 
							
								get-mappings [ init-mappings ] unless 
							 |