Skip to content

Commit f443211

Browse files
author
Richard Jones
committed
Implemented proper datatypes in mysql and postgresql backends...
...(well, sqlite too, but that doesn't care). Probably should use BOOLEAN instead of INTEGER for the Boolean props. Need to fix a bizzaro MySQL error (gee, how unusual) Need to finish MySQL migration from "version 1" database schemas.
1 parent 95de349 commit f443211

File tree

10 files changed

+539
-309
lines changed

10 files changed

+539
-309
lines changed

roundup/backends/back_metakit.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# $Id: back_metakit.py,v 1.66 2004-03-21 23:39:08 richard Exp $
1+
# $Id: back_metakit.py,v 1.67 2004-03-22 07:45:39 richard Exp $
22
'''Metakit backend for Roundup, originally by Gordon McMillan.
33
44
Known Current Bugs:
@@ -39,7 +39,7 @@
3939
__docformat__ = 'restructuredtext'
4040
# Enable this flag to break backwards compatibility (i.e. can't read old
4141
# databases) but comply with more roundup features, like adding NULL support.
42-
BACKWARDS_COMPATIBLE = True
42+
BACKWARDS_COMPATIBLE = 1
4343

4444
from roundup import hyperdb, date, password, roundupdb, security
4545
import metakit

roundup/backends/back_mysql.py

Lines changed: 191 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,31 @@
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

1236
from 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+
281410
class 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

491621
class Class(MysqlClass, rdbms_common.Class):
492622
pass

0 commit comments

Comments
 (0)