Skip to content

Commit 196bf96

Browse files
author
Richard Jones
committed
PostgreSQL backend lands.
- that's the postgresql backend in (cleaned up doc, unit testing harness and the backend module itself) - also cleaned up the index maintenance code (actual checks for existence rather than bare-except failure mode)
1 parent c8bb309 commit 196bf96

File tree

9 files changed

+268
-161
lines changed

9 files changed

+268
-161
lines changed

CHANGES.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Feature:
77
- support setgid and running on port < 1024 (sf patch 777528)
88
- using Zope3's test runner now, allowing GC checks, nicer controls and
99
coverage analysis
10-
- !BETA! added postgresql backend, needs work !BETA!
10+
- added postgresql backend
1111
- all RDBMS backends now have indexes on several columns
1212
- Change nosymessage and send_message to accept msgid=None (RFE #707235).
1313

doc/postgresql.txt

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,29 +18,40 @@ additionally install:
1818
http://initd.org/software/initd/psycopg
1919

2020

21+
Running the PostgreSQL unit tests
22+
=================================
23+
24+
The user that you're running the tests as will need to be able to access
25+
the postgresql database on the local machine and create and drop
26+
databases. Edit the ``test/test_postgresql.py`` database connection info if
27+
you wish to test against a different database.
28+
29+
The test database will be called "rounduptest".
30+
31+
2132
Additional configuration
2233
========================
2334

2435
To initialise and use PostgreSQL database roundup's configuration file
2536
(config.py in the tracker's home directory) should be appended with the
2637
following constants (substituting real values, obviously)::
2738

28-
POSTGRESQL_DBHOST = 'localhost'
29-
POSTGRESQL_DBUSER = 'roundup'
30-
POSTGRESQL_DBPASSWORD = 'roundup'
31-
POSTGRESQL_DBNAME = 'roundup'
32-
POSTGRESQL_PORT = 5432
39+
POSTGRESQL_DATABASE = {'database': 'rounduptest'}
40+
41+
if not local, or a different user is to be used, then more information may
42+
be supplied::
43+
3344
POSTGRESQL_DATABASE = {
34-
'host': MYSQL_DBHOST, 'port': POSTGRESQL_PORT,
35-
'user': MYSQL_DBUSER, 'password': MYSQL_DBPASSWORD,
36-
'database': MYSQL_DBNAME
45+
'host': 'localhost', 'port': 5432,
46+
'database': 'roundup'
47+
'user': 'roundup', 'password': 'roundup',
3748
}
3849

3950
Also note that you can leave some values out of
4051
``POSTGRESQL_DATABASE``: 'host' and 'port' are not necessary when
4152
connecting to a local database and 'password'
4253
is optional if postgres trusts local connections. The user specified in
43-
``POSTGRESQL_DBUSER`` must have rights to create a new database and to
54+
``user`` must have rights to create a new database and to
4455
connect to the "template1" database, used while initializing roundup.
4556

4657
Have fun with psycopg,

roundup/backends/back_mysql.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,14 @@ def sql_fetchone(self):
9292

9393
def sql_fetchall(self):
9494
return self.cursor.fetchall()
95-
95+
96+
def sql_index_exists(self, table_name, index_name):
97+
self.cursor.execute('show index from %s'%table_name)
98+
for index in self.cursor.fetchall():
99+
if index[2] == index_name:
100+
return 1
101+
return 0
102+
96103
def save_dbschema(self, schema):
97104
s = repr(self.database_schema)
98105
self.sql('INSERT INTO schema VALUES (%s)', (s,))
@@ -138,8 +145,24 @@ def create_class_table(self, spec):
138145
if __debug__:
139146
print >>hyperdb.DEBUG, 'create_class', (self, sql)
140147
self.cursor.execute(sql)
148+
self.create_class_table_indexes(spec)
141149
return cols, mls
142150

151+
def drop_class_table_indexes(self, cn, key):
152+
# drop the old table indexes first
153+
l = ['_%s_id_idx'%cn, '_%s_retired_idx'%cn]
154+
if key:
155+
l.append('_%s_%s_idx'%(cn, key))
156+
157+
table_name = '_%s'%cn
158+
for index_name in l:
159+
if not self.sql_index_exists(table_name, index_name):
160+
continue
161+
index_sql = 'drop index %s on %s'%(index_name, table_name)
162+
if __debug__:
163+
print >>hyperdb.DEBUG, 'drop_index', (self, index_sql)
164+
self.cursor.execute(index_sql)
165+
143166
def create_journal_table(self, spec):
144167
cols = ',' . join(['`%s` VARCHAR(255)'%x
145168
for x in 'nodeid date tag action params' . split()])
@@ -148,6 +171,16 @@ def create_journal_table(self, spec):
148171
if __debug__:
149172
print >>hyperdb.DEBUG, 'create_class', (self, sql)
150173
self.cursor.execute(sql)
174+
self.create_journal_table_indexes(spec)
175+
176+
def drop_journal_table_indexes(self, classname):
177+
index_name = '%s_journ_idx'%classname
178+
if not self.sql_index_exists('%s__journal'%classname, index_name):
179+
return
180+
index_sql = 'drop index %s on %s__journal'%(index_name, classname)
181+
if __debug__:
182+
print >>hyperdb.DEBUG, 'drop_index', (self, index_sql)
183+
self.cursor.execute(index_sql)
151184

152185
def create_multilink_table(self, spec, ml):
153186
sql = '''CREATE TABLE `%s_%s` (linkid VARCHAR(255),
@@ -156,6 +189,20 @@ def create_multilink_table(self, spec, ml):
156189
if __debug__:
157190
print >>hyperdb.DEBUG, 'create_class', (self, sql)
158191
self.cursor.execute(sql)
192+
self.create_multilink_table_indexes(spec, ml)
193+
194+
def drop_multilink_table_indexes(self, classname, ml):
195+
l = [
196+
'%s_%s_l_idx'%(classname, ml),
197+
'%s_%s_n_idx'%(classname, ml)
198+
]
199+
for index_name in l:
200+
if not self.sql_index_exists(table_name, index_name):
201+
continue
202+
index_sql = 'drop index %s on %s'%(index_name, table_name)
203+
if __debug__:
204+
print >>hyperdb.DEBUG, 'drop_index', (self, index_sql)
205+
self.cursor.execute(index_sql)
159206

160207
class MysqlClass:
161208
# we're overriding this method for ONE missing bit of functionality.

roundup/backends/back_postgresql.py

Lines changed: 13 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,7 @@
1111
from roundup.backends.rdbms_common import *
1212
from roundup.backends import rdbms_common
1313
import psycopg
14-
import os, shutil
15-
16-
class Maintenance:
17-
""" Database maintenance functions """
18-
def db_nuke(self, config):
19-
"""Clear all database contents and drop database itself"""
20-
config.POSTGRESQL_DATABASE['database'] = 'template1'
21-
db = Database(config, 'admin')
22-
db.conn.set_isolation_level(0)
23-
db.sql("DROP DATABASE %s" % config.POSTGRESQL_DBNAME)
24-
db.sql("CREATE DATABASE %s" % config.POSTGRESQL_DBNAME)
25-
if os.path.exists(config.DATABASE):
26-
shutil.rmtree(config.DATABASE)
27-
config.POSTGRESQL_DATABASE['database'] = config.POSTGRESQL_DBNAME
28-
29-
def db_exists(self, config):
30-
"""Check if database already exists"""
31-
try:
32-
db = Database(config, 'admin')
33-
return 1
34-
except:
35-
return 0
14+
import os, shutil, popen2
3615

3716
class Database(Database):
3817
arg = '%s'
@@ -58,7 +37,7 @@ def close(self):
5837
self.conn.close()
5938

6039
def __repr__(self):
61-
return '<psycopgroundsql 0x%x>' % id(self)
40+
return '<roundpsycopgsql 0x%x>' % id(self)
6241

6342
def sql_fetchone(self):
6443
return self.cursor.fetchone()
@@ -67,7 +46,15 @@ def sql_fetchall(self):
6746
return self.cursor.fetchall()
6847

6948
def sql_stringquote(self, value):
70-
return psycopg.QuotedString(str(value))
49+
''' psycopg.QuotedString returns a "buffer" object with the
50+
single-quotes around it... '''
51+
return str(psycopg.QuotedString(str(value)))[1:-1]
52+
53+
def sql_index_exists(self, table_name, index_name):
54+
sql = 'select count(*) from pg_indexes where ' \
55+
'tablename=%s and indexname=%s'%(self.arg, self.arg)
56+
self.cursor.execute(sql, (table_name, index_name))
57+
return self.cursor.fetchone()[0]
7158

7259
def save_dbschema(self, schema):
7360
s = repr(self.database_schema)
@@ -139,10 +126,6 @@ def create_multilink_table(self, spec, ml):
139126

140127
self.cursor.execute(sql)
141128

142-
# Static methods
143-
nuke = Maintenance().db_nuke
144-
exists = Maintenance().db_exists
145-
146129
class PsycopgClass:
147130
def find(self, **propspec):
148131
"""Get the ids of nodes in this class which link to the given nodes."""
@@ -174,6 +157,8 @@ def find(self, **propspec):
174157
if type(values) is type(''):
175158
allvalues += (values,)
176159
where.append('_%s = %s' % (prop, a))
160+
elif values is None:
161+
where.append('_%s is NULL'%prop)
177162
else:
178163
allvalues += tuple(values.keys())
179164
where.append('_%s in (%s)' % (prop, ','.join([a]*len(values))))

roundup/backends/back_sqlite.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# $Id: back_sqlite.py,v 1.10 2003-10-07 07:17:54 anthonybaxter Exp $
1+
# $Id: back_sqlite.py,v 1.11 2003-11-11 11:19:18 richard Exp $
22
__doc__ = '''
33
See https://pysqlite.sourceforge.net/ for pysqlite info
44
'''
@@ -106,6 +106,13 @@ def sql_commit(self):
106106
if str(error) != 'cannot commit - no transaction is active':
107107
raise
108108

109+
def sql_index_exists(self, table_name, index_name):
110+
self.cursor.execute('pragma index_list(%s)'%table_name)
111+
for entry in self.cursor.fetchall():
112+
if entry[1] == index_name:
113+
return 1
114+
return 0
115+
109116
def save_dbschema(self, schema):
110117
''' Save the schema definition that the database currently implements
111118
'''

0 commit comments

Comments
 (0)