Mercurial > p > roundup > code
comparison roundup/backends/back_postgresql.py @ 1873:f63aa57386b0
Backend improvements.
- using Zope3's test runner now, allowing GC checks, nicer controls and
coverage analysis
- all RDMBS backends now have indexes on several columns
- added testing of schema mutation, fixed rdbms backends handling of a
couple of cases
- !BETA! added postgresql backend, needs work !BETA!
| author | Richard Jones <richard@users.sourceforge.net> |
|---|---|
| date | Sat, 25 Oct 2003 22:53:26 +0000 |
| parents | |
| children | f255363e6d97 |
comparison
equal
deleted
inserted
replaced
| 1872:c085b4f4f0c0 | 1873:f63aa57386b0 |
|---|---|
| 1 # | |
| 2 # Copyright (c) 2003 Martynas Sklyzmantas, Andrey Lebedev <andrey@micro.lt> | |
| 3 # | |
| 4 # This module is free software, and you may redistribute it and/or modify | |
| 5 # under the same terms as Python, so long as this copyright message and | |
| 6 # disclaimer are retained in their original form. | |
| 7 # | |
| 8 # psycopg backend for roundup | |
| 9 # | |
| 10 | |
| 11 from roundup.backends.rdbms_common import * | |
| 12 from roundup.backends import rdbms_common | |
| 13 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 | |
| 36 | |
| 37 class Database(Database): | |
| 38 arg = '%s' | |
| 39 | |
| 40 def open_connection(self): | |
| 41 db = getattr(self.config, 'POSTGRESQL_DATABASE') | |
| 42 try: | |
| 43 self.conn = psycopg.connect(**db) | |
| 44 except psycopg.OperationalError, message: | |
| 45 raise DatabaseError, message | |
| 46 | |
| 47 self.cursor = self.conn.cursor() | |
| 48 | |
| 49 try: | |
| 50 self.database_schema = self.load_dbschema() | |
| 51 except: | |
| 52 self.rollback() | |
| 53 self.database_schema = {} | |
| 54 self.sql("CREATE TABLE schema (schema TEXT)") | |
| 55 self.sql("CREATE TABLE ids (name VARCHAR(255), num INT4)") | |
| 56 | |
| 57 def close(self): | |
| 58 self.conn.close() | |
| 59 | |
| 60 def __repr__(self): | |
| 61 return '<psycopgroundsql 0x%x>' % id(self) | |
| 62 | |
| 63 def sql_fetchone(self): | |
| 64 return self.cursor.fetchone() | |
| 65 | |
| 66 def sql_fetchall(self): | |
| 67 return self.cursor.fetchall() | |
| 68 | |
| 69 def sql_stringquote(self, value): | |
| 70 return psycopg.QuotedString(str(value)) | |
| 71 | |
| 72 def save_dbschema(self, schema): | |
| 73 s = repr(self.database_schema) | |
| 74 self.sql('INSERT INTO schema VALUES (%s)', (s,)) | |
| 75 | |
| 76 def load_dbschema(self): | |
| 77 self.cursor.execute('SELECT schema FROM schema') | |
| 78 schema = self.cursor.fetchone() | |
| 79 if schema: | |
| 80 return eval(schema[0]) | |
| 81 | |
| 82 def save_journal(self, classname, cols, nodeid, journaldate, | |
| 83 journaltag, action, params): | |
| 84 params = repr(params) | |
| 85 entry = (nodeid, journaldate, journaltag, action, params) | |
| 86 | |
| 87 a = self.arg | |
| 88 sql = 'INSERT INTO %s__journal (%s) values (%s, %s, %s, %s, %s)'%( | |
| 89 classname, cols, a, a, a, a, a) | |
| 90 | |
| 91 if __debug__: | |
| 92 print >>hyperdb.DEBUG, 'addjournal', (self, sql, entry) | |
| 93 | |
| 94 self.cursor.execute(sql, entry) | |
| 95 | |
| 96 def load_journal(self, classname, cols, nodeid): | |
| 97 sql = 'SELECT %s FROM %s__journal WHERE nodeid = %s' % ( | |
| 98 cols, classname, self.arg) | |
| 99 | |
| 100 if __debug__: | |
| 101 print >>hyperdb.DEBUG, 'getjournal', (self, sql, nodeid) | |
| 102 | |
| 103 self.cursor.execute(sql, (nodeid,)) | |
| 104 res = [] | |
| 105 for nodeid, date_stamp, user, action, params in self.cursor.fetchall(): | |
| 106 params = eval(params) | |
| 107 res.append((nodeid, date.Date(date_stamp), user, action, params)) | |
| 108 return res | |
| 109 | |
| 110 def create_class_table(self, spec): | |
| 111 cols, mls = self.determine_columns(spec.properties.items()) | |
| 112 cols.append('id') | |
| 113 cols.append('__retired__') | |
| 114 scols = ',' . join(['"%s" VARCHAR(255)' % x for x in cols]) | |
| 115 sql = 'CREATE TABLE "_%s" (%s)' % (spec.classname, scols) | |
| 116 | |
| 117 if __debug__: | |
| 118 print >>hyperdb.DEBUG, 'create_class', (self, sql) | |
| 119 | |
| 120 self.cursor.execute(sql) | |
| 121 return cols, mls | |
| 122 | |
| 123 def create_journal_table(self, spec): | |
| 124 cols = ',' . join(['"%s" VARCHAR(255)' % x | |
| 125 for x in 'nodeid date tag action params' . split()]) | |
| 126 sql = 'CREATE TABLE "%s__journal" (%s)'%(spec.classname, cols) | |
| 127 | |
| 128 if __debug__: | |
| 129 print >>hyperdb.DEBUG, 'create_class', (self, sql) | |
| 130 | |
| 131 self.cursor.execute(sql) | |
| 132 | |
| 133 def create_multilink_table(self, spec, ml): | |
| 134 sql = '''CREATE TABLE "%s_%s" (linkid VARCHAR(255), | |
| 135 nodeid VARCHAR(255))''' % (spec.classname, ml) | |
| 136 | |
| 137 if __debug__: | |
| 138 print >>hyperdb.DEBUG, 'create_class', (self, sql) | |
| 139 | |
| 140 self.cursor.execute(sql) | |
| 141 | |
| 142 # Static methods | |
| 143 nuke = Maintenance().db_nuke | |
| 144 exists = Maintenance().db_exists | |
| 145 | |
| 146 class PsycopgClass: | |
| 147 def find(self, **propspec): | |
| 148 """Get the ids of nodes in this class which link to the given nodes.""" | |
| 149 | |
| 150 if __debug__: | |
| 151 print >>hyperdb.DEBUG, 'find', (self, propspec) | |
| 152 | |
| 153 # shortcut | |
| 154 if not propspec: | |
| 155 return [] | |
| 156 | |
| 157 # validate the args | |
| 158 props = self.getprops() | |
| 159 propspec = propspec.items() | |
| 160 for propname, nodeids in propspec: | |
| 161 # check the prop is OK | |
| 162 prop = props[propname] | |
| 163 if not isinstance(prop, Link) and not isinstance(prop, Multilink): | |
| 164 raise TypeError, "'%s' not a Link/Multilink property"%propname | |
| 165 | |
| 166 # first, links | |
| 167 l = [] | |
| 168 where = [] | |
| 169 allvalues = () | |
| 170 a = self.db.arg | |
| 171 for prop, values in propspec: | |
| 172 if not isinstance(props[prop], hyperdb.Link): | |
| 173 continue | |
| 174 if type(values) is type(''): | |
| 175 allvalues += (values,) | |
| 176 where.append('_%s = %s' % (prop, a)) | |
| 177 else: | |
| 178 allvalues += tuple(values.keys()) | |
| 179 where.append('_%s in (%s)' % (prop, ','.join([a]*len(values)))) | |
| 180 tables = [] | |
| 181 if where: | |
| 182 self.db.sql('SELECT id AS nodeid FROM _%s WHERE %s' % ( | |
| 183 self.classname, ' and '.join(where)), allvalues) | |
| 184 l += [x[0] for x in self.db.sql_fetchall()] | |
| 185 | |
| 186 # now multilinks | |
| 187 for prop, values in propspec: | |
| 188 vals = () | |
| 189 if not isinstance(props[prop], hyperdb.Multilink): | |
| 190 continue | |
| 191 if type(values) is type(''): | |
| 192 vals = (values,) | |
| 193 s = a | |
| 194 else: | |
| 195 vals = tuple(values.keys()) | |
| 196 s = ','.join([a]*len(values)) | |
| 197 query = 'SELECT nodeid FROM %s_%s WHERE linkid IN (%s)'%( | |
| 198 self.classname, prop, s) | |
| 199 self.db.sql(query, vals) | |
| 200 l += [x[0] for x in self.db.sql_fetchall()] | |
| 201 | |
| 202 if __debug__: | |
| 203 print >>hyperdb.DEBUG, 'find ... ', l | |
| 204 | |
| 205 # Remove duplicated ids | |
| 206 d = {} | |
| 207 for k in l: | |
| 208 d[k] = 1 | |
| 209 return d.keys() | |
| 210 | |
| 211 return l | |
| 212 | |
| 213 class Class(PsycopgClass, rdbms_common.Class): | |
| 214 pass | |
| 215 class IssueClass(PsycopgClass, rdbms_common.IssueClass): | |
| 216 pass | |
| 217 class FileClass(PsycopgClass, rdbms_common.FileClass): | |
| 218 pass | |
| 219 |
