66# disclaimer are retained in their original form.
77#
88
9- '''This module defines a backend implementation for MySQL.'''
9+ '''This module defines a backend implementation for MySQL.
10+
11+
12+ How to implement AUTO_INCREMENT:
13+
14+ mysql> create table foo (num integer auto_increment primary key, name
15+ varchar(255)) AUTO_INCREMENT=1 type=InnoDB;
16+
17+ ql> insert into foo (name) values ('foo5');
18+ Query OK, 1 row affected (0.00 sec)
19+
20+ mysql> SELECT num FROM foo WHERE num IS NULL;
21+ +-----+
22+ | num |
23+ +-----+
24+ | 4 |
25+ +-----+
26+ 1 row in set (0.00 sec)
27+
28+ mysql> SELECT num FROM foo WHERE num IS NULL;
29+ Empty set (0.00 sec)
30+
31+ NOTE: we don't need an index on the id column if it's PRIMARY KEY
32+
33+ '''
1034__docformat__ = 'restructuredtext'
1135
1236from roundup .backends .rdbms_common import *
@@ -87,6 +111,17 @@ class Database(Database):
87111 mysql_backend = 'InnoDB'
88112 #mysql_backend = 'BDB'
89113
114+ hyperdb_to_sql_value = {
115+ hyperdb .String : str ,
116+ # no fractional seconds for MySQL
117+ hyperdb .Date : lambda x : x .formal (sep = ' ' ),
118+ hyperdb .Link : int ,
119+ hyperdb .Interval : lambda x : x .serialise (),
120+ hyperdb .Password : str ,
121+ hyperdb .Boolean : int ,
122+ hyperdb .Number : lambda x : x ,
123+ }
124+
90125 def sql_open_connection (self ):
91126 db = getattr (self .config , 'MYSQL_DATABASE' )
92127 try :
@@ -116,43 +151,113 @@ def open_connection(self):
116151 self .init_dbschema ()
117152 self .sql ("CREATE TABLE schema (schema TEXT) TYPE=%s" %
118153 self .mysql_backend )
119- # TODO: use AUTO_INCREMENT for generating ids:
120- # http://www.mysql.com/doc/en/CREATE_TABLE.html
121- self .sql ("CREATE TABLE ids (name varchar(255), num INT) TYPE=%s" %
122- self .mysql_backend )
123- self .sql ("CREATE INDEX ids_name_idx ON ids(name)" )
154+ self .cursor .execute ('''CREATE TABLE ids (name VARCHAR(255),
155+ num INTEGER) TYPE=%s''' % self .mysql_backend )
156+ self .cursor .execute ('create index ids_name_idx on ids(name)' )
124157 self .create_version_2_tables ()
125158
126159 def create_version_2_tables (self ):
127160 # OTK store
128- self .cursor .execute ('CREATE TABLE otks (otk_key VARCHAR(255), '
129- ' otk_value VARCHAR(255), otk_time FLOAT(20)) '
130- ' TYPE=%s'% self .mysql_backend )
161+ self .cursor .execute (''' CREATE TABLE otks (otk_key VARCHAR(255),
162+ otk_value VARCHAR(255), otk_time FLOAT(20))
163+ TYPE=%s'' ' % self .mysql_backend )
131164 self .cursor .execute ('CREATE INDEX otks_key_idx ON otks(otk_key)' )
132165
133166 # Sessions store
134- self .cursor .execute ('CREATE TABLE sessions (session_key VARCHAR(255), '
135- 'session_time FLOAT(20 ), session_value VARCHAR(255)) '
136- ' TYPE=%s'% self .mysql_backend )
137- self .cursor .execute ('CREATE INDEX sessions_key_idx ON '
138- ' sessions(session_key)' )
167+ self .cursor .execute (''' CREATE TABLE sessions (
168+ session_key VARCHAR(255 ), session_time FLOAT(20),
169+ session_value VARCHAR(255)) TYPE=%s'' ' % self .mysql_backend )
170+ self .cursor .execute (''' CREATE INDEX sessions_key_idx ON
171+ sessions(session_key)'' ' )
139172
140173 # full-text indexing store
141- self .cursor .execute ('CREATE TABLE _textids (_class VARCHAR(255), '
142- ' _itemid VARCHAR(255), _prop VARCHAR(255), _textid INT) '
143- ' TYPE=%s'% self .mysql_backend )
144- self .cursor .execute ('CREATE TABLE _words (_word VARCHAR(30), '
145- ' _textid INT) TYPE=%s'% self .mysql_backend )
146- self .cursor .execute ('CREATE INDEX words_word_ids ON _words (_word)' )
174+ self .cursor .execute (''' CREATE TABLE __textids (_class VARCHAR(255),
175+ _itemid VARCHAR(255), _prop VARCHAR(255), _textid INT)
176+ TYPE=%s'' ' % self .mysql_backend )
177+ self .cursor .execute (''' CREATE TABLE __words (_word VARCHAR(30),
178+ _textid INT) TYPE=%s'' ' % self .mysql_backend )
179+ self .cursor .execute ('CREATE INDEX words_word_ids ON __words (_word)' )
147180 sql = 'insert into ids (name, num) values (%s,%s)' % (self .arg , self .arg )
148- self .cursor .execute (sql , ('_textids ' , 1 ))
181+ self .cursor .execute (sql , ('__textids ' , 1 ))
149182
150183 def add_actor_column (self ):
151- # update existing tables to have the new actor column
152- tables = self .database_schema ['tables' ]
153- for name in tables .keys ():
154- self .cursor .execute ('ALTER TABLE _%s add __actor '
155- 'VARCHAR(255)' % name )
184+ ''' While we're adding the actor column, we need to update the
185+ tables to have the correct datatypes.'''
186+ assert 0 , 'FINISH ME!'
187+
188+ for spec in self .classes .values ():
189+ new_has = spec .properties .has_key
190+ new_spec = spec .schema ()
191+ new_spec [1 ].sort ()
192+ old_spec [1 ].sort ()
193+ if not force and new_spec == old_spec :
194+ # no changes
195+ return 0
196+
197+ if __debug__ :
198+ print >> hyperdb .DEBUG , 'update_class FIRING'
199+
200+ # detect multilinks that have been removed, and drop their table
201+ old_has = {}
202+ for name ,prop in old_spec [1 ]:
203+ old_has [name ] = 1
204+ if new_has (name ) or not isinstance (prop , hyperdb .Multilink ):
205+ continue
206+ # it's a multilink, and it's been removed - drop the old
207+ # table. First drop indexes.
208+ self .drop_multilink_table_indexes (spec .classname , ml )
209+ sql = 'drop table %s_%s' % (spec .classname , prop )
210+ if __debug__ :
211+ print >> hyperdb .DEBUG , 'update_class' , (self , sql )
212+ self .cursor .execute (sql )
213+ old_has = old_has .has_key
214+
215+ # now figure how we populate the new table
216+ if adding_actor :
217+ fetch = ['_activity' , '_creation' , '_creator' ]
218+ else :
219+ fetch = ['_actor' , '_activity' , '_creation' , '_creator' ]
220+ properties = spec .getprops ()
221+ for propname ,x in new_spec [1 ]:
222+ prop = properties [propname ]
223+ if isinstance (prop , hyperdb .Multilink ):
224+ if force or not old_has (propname ):
225+ # we need to create the new table
226+ self .create_multilink_table (spec , propname )
227+ elif old_has (propname ):
228+ # we copy this col over from the old table
229+ fetch .append ('_' + propname )
230+
231+ # select the data out of the old table
232+ fetch .append ('id' )
233+ fetch .append ('__retired__' )
234+ fetchcols = ',' .join (fetch )
235+ cn = spec .classname
236+ sql = 'select %s from _%s' % (fetchcols , cn )
237+ if __debug__ :
238+ print >> hyperdb .DEBUG , 'update_class' , (self , sql )
239+ self .cursor .execute (sql )
240+ olddata = self .cursor .fetchall ()
241+
242+ # TODO: update all the other index dropping code
243+ self .drop_class_table_indexes (cn , old_spec [0 ])
244+
245+ # drop the old table
246+ self .cursor .execute ('drop table _%s' % cn )
247+
248+ # create the new table
249+ self .create_class_table (spec )
250+
251+ # do the insert of the old data - the new columns will have
252+ # NULL values
253+ args = ',' .join ([self .arg for x in fetch ])
254+ sql = 'insert into _%s (%s) values (%s)' % (cn , fetchcols , args )
255+ if __debug__ :
256+ print >> hyperdb .DEBUG , 'update_class' , (self , sql , olddata [0 ])
257+ for entry in olddata :
258+ self .cursor .execute (sql , tuple (entry ))
259+
260+ return 1
156261
157262 def __repr__ (self ):
158263 return '<myroundsql 0x%x>' % id (self )
@@ -174,40 +279,21 @@ def save_dbschema(self, schema):
174279 s = repr (self .database_schema )
175280 self .sql ('INSERT INTO schema VALUES (%s)' , (s ,))
176281
177- def save_journal (self , classname , cols , nodeid , journaldate ,
178- journaltag , action , params ):
179- params = repr (params )
180- entry = (nodeid , journaldate , journaltag , action , params )
181-
182- a = self .arg
183- sql = 'insert into %s__journal (%s) values (%s,%s,%s,%s,%s)' % (classname ,
184- cols , a , a , a , a , a )
185- if __debug__ :
186- print >> hyperdb .DEBUG , 'addjournal' , (self , sql , entry )
187- self .cursor .execute (sql , entry )
188-
189- def load_journal (self , classname , cols , nodeid ):
190- sql = 'select %s from %s__journal where nodeid=%s' % (cols , classname ,
191- self .arg )
192- if __debug__ :
193- print >> hyperdb .DEBUG , 'getjournal' , (self , sql , nodeid )
194- self .cursor .execute (sql , (nodeid ,))
195- res = []
196- for nodeid , date_stamp , user , action , params in self .cursor .fetchall ():
197- params = eval (params )
198- res .append ((nodeid , date .Date (date_stamp ), user , action , params ))
199- return res
200-
201282 def create_class_table (self , spec ):
202283 cols , mls = self .determine_columns (spec .properties .items ())
203- cols .append ('id' )
204- cols .append ('__retired__' )
205- scols = ',' . join (['`%s` VARCHAR(255)' % x for x in cols ])
206- sql = 'CREATE TABLE `_%s` (%s) TYPE=%s' % (spec .classname , scols ,
284+
285+ # add on our special columns
286+ cols .append (('id' , 'INTEGER PRIMARY KEY' ))
287+ cols .append (('__retired__' , 'INTEGER DEFAULT 0' ))
288+
289+ # create the base table
290+ scols = ',' .join (['%s %s' % x for x in cols ])
291+ sql = 'create table _%s (%s) type=%s' % (spec .classname , scols ,
207292 self .mysql_backend )
208293 if __debug__ :
209- print >> hyperdb .DEBUG , 'create_class' , (self , sql )
294+ print >> hyperdb .DEBUG , 'create_class' , (self , sql )
210295 self .cursor .execute (sql )
296+
211297 self .create_class_table_indexes (spec )
212298 return cols , mls
213299
@@ -227,12 +313,15 @@ def drop_class_table_indexes(self, cn, key):
227313 self .cursor .execute (index_sql )
228314
229315 def create_journal_table (self , spec ):
230- cols = ',' . join (['`%s` VARCHAR(255)' % x
231- for x in 'nodeid date tag action params' . split ()])
232- sql = 'CREATE TABLE `%s__journal` (%s) TYPE=%s' % (spec .classname ,
233- cols , self .mysql_backend )
316+ # journal table
317+ cols = ',' .join (['%s varchar' % x
318+ for x in 'nodeid date tag action params' .split ()])
319+ sql = '''create table %s__journal (
320+ nodeid integer, date timestamp, tag varchar(255),
321+ action varchar(255), params varchar(255)) type=%s''' % (
322+ spec .classname , self .mysql_backend )
234323 if __debug__ :
235- print >> hyperdb .DEBUG , 'create_class ' , (self , sql )
324+ print >> hyperdb .DEBUG , 'create_journal_table ' , (self , sql )
236325 self .cursor .execute (sql )
237326 self .create_journal_table_indexes (spec )
238327
@@ -278,6 +367,46 @@ def drop_class_table_key_index(self, cn, key):
278367 print >> hyperdb .DEBUG , 'drop_index' , (self , sql )
279368 self .cursor .execute (sql )
280369
370+ # old-skool id generation
371+ def newid (self , classname ):
372+ ''' Generate a new id for the given class
373+ '''
374+ # get the next ID
375+ sql = 'select num from ids where name=%s' % self .arg
376+ if __debug__ :
377+ print >> hyperdb .DEBUG , 'newid' , (self , sql , classname )
378+ self .cursor .execute (sql , (classname , ))
379+ newid = int (self .cursor .fetchone ()[0 ])
380+
381+ # update the counter
382+ sql = 'update ids set num=%s where name=%s' % (self .arg , self .arg )
383+ vals = (int (newid )+ 1 , classname )
384+ if __debug__ :
385+ print >> hyperdb .DEBUG , 'newid' , (self , sql , vals )
386+ self .cursor .execute (sql , vals )
387+
388+ # return as string
389+ return str (newid )
390+
391+ def setid (self , classname , setid ):
392+ ''' Set the id counter: used during import of database
393+
394+ We add one to make it behave like the seqeunces in postgres.
395+ '''
396+ sql = 'update ids set num=%s where name=%s' % (self .arg , self .arg )
397+ vals = (int (setid )+ 1 , classname )
398+ if __debug__ :
399+ print >> hyperdb .DEBUG , 'setid' , (self , sql , vals )
400+ self .cursor .execute (sql , vals )
401+
402+ def create_class (self , spec ):
403+ rdbms_common .Database .create_class (self , spec )
404+ sql = 'insert into ids (name, num) values (%s, %s)'
405+ vals = (spec .classname , 1 )
406+ if __debug__ :
407+ print >> hyperdb .DEBUG , 'create_class' , (self , sql , vals )
408+ self .cursor .execute (sql , vals )
409+
281410class MysqlClass :
282411 # we're overriding this method for ONE missing bit of functionality.
283412 # look for "I can't believe it's not a toy RDBMS" below
@@ -486,7 +615,8 @@ def filter(self, search_matches, filterspec, sort=(None,None),
486615 l = self .db .cursor .fetchall ()
487616
488617 # return the IDs (the first column)
489- return [row [0 ] for row in l ]
618+ # XXX numeric ids
619+ return [str (row [0 ]) for row in l ]
490620
491621class Class (MysqlClass , rdbms_common .Class ):
492622 pass
0 commit comments