/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/Model/transientdb.py
ViewVC logotype

Contents of /branches/WIP-pyshapelib-bramz/Thuban/Model/transientdb.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 777 - (show annotations)
Tue Apr 29 14:54:12 2003 UTC (21 years, 10 months ago) by bh
Original Path: trunk/thuban/Thuban/Model/transientdb.py
File MIME type: text/x-python
File size: 12388 byte(s)
(TransientDatabase.close): Set
self.conn to None after closing the connection to make sure it's
not closed twice

1 # Copyright (C) 2003 by Intevation GmbH
2 # Authors:
3 # Bernhard Herzog <[email protected]>
4 #
5 # This program is free software under the GPL (>=v2)
6 # Read the file COPYING coming with the software for details.
7
8 """Database for transient data
9
10 This database is intended for data representations needed during the
11 lifetime of a Thuban session but which is not permanent. Examples of
12 this are for instance a join of two DBF files where the DBF files are
13 the permanent representation of the data and the join only exists in the
14 Thuban session and is reconstructed when the session is opened.
15 """
16
17 __version__ = "$Revision$"
18 # $Source$
19 # $Id$
20
21 import os
22 import weakref
23 from sqlite import connect
24
25 import table
26
27 sql_type_map = {
28 table.FIELDTYPE_INT: "INTEGER",
29 table.FIELDTYPE_STRING: "VARCHAR",
30 table.FIELDTYPE_DOUBLE: "FLOAT",
31 }
32
33 type_converter_map = {
34 table.FIELDTYPE_INT: int,
35 table.FIELDTYPE_STRING: str,
36 table.FIELDTYPE_DOUBLE: float,
37 }
38
39 class TransientDatabase:
40
41 def __init__(self, filename):
42 self.filename = filename
43 self.conn = connect(filename)
44 # Counters to produce unique table and column names
45 self.num_tables = 0
46 self.num_cols = 0
47 # Since there's only once process using the SQLite database, we
48 # might be able to get a tad more speed with default_synchronous
49 # OFF. So far I haven't seen any measurable speedup, though.
50 #self.execute("PRAGMA default_synchronous = OFF")
51
52 def __del__(self):
53 self.close()
54
55 def close(self):
56 if self.conn is not None:
57 self.conn.close()
58 self.conn = None
59
60 def new_table_name(self):
61 self.num_tables += 1
62 return "Table%03d" % self.num_tables
63
64 def new_column_name(self):
65 self.num_cols += 1
66 return "Col%03d" % self.num_cols
67
68 def execute(self, *args):
69 """execute the SQL statement in the database and return the result"""
70 cursor = self.conn.cursor()
71 cursor.execute(*args)
72 result = cursor.fetchone()
73 self.conn.commit()
74 return result
75
76 def cursor(self):
77 return self.conn.cursor()
78
79
80 class ColumnReference:
81
82 def __init__(self, name, type, internal_name):
83 self.name = name
84 self.type = type
85 self.internal_name = internal_name
86
87
88 class TransientTableBase:
89
90 """Base class for tables in the transient database"""
91
92 def __init__(self, transient_db):
93 """Initialize the table for use with the given transient db"""
94 self.db = transient_db
95 self.tablename = self.db.new_table_name()
96 self.indexed_columns = {}
97 self.read_record_cursor = None
98 self.read_record_last_row = None
99
100 def create(self, columns):
101 self.columns = columns
102 self.name_to_column = {}
103 self.orig_names = []
104 self.internal_to_orig = {}
105 self.orig_to_internal = {}
106
107 # Create the column objects and fill various maps and lists
108 for col in self.columns:
109 self.name_to_column[col.name] = col
110 self.orig_names.append(col.name)
111 self.internal_to_orig[col.internal_name] = col.name
112 self.orig_to_internal[col.name] = col.internal_name
113
114 # Build the CREATE TABLE statement and create the table in the
115 # database
116 table_types = []
117 for col in self.columns:
118 table_types.append("%s %s" % (col.internal_name,
119 sql_type_map[col.type]))
120 table_stmt = "CREATE TABLE %s (\n %s\n);" % (self.tablename,
121 ",\n ".join(table_types))
122 self.db.execute(table_stmt)
123
124 def transient_table(self):
125 """
126 Return a table whose underlying implementation is in the transient db
127 """
128 return self
129
130 def ensure_index(self, column):
131 """Ensure that there's an index on the given column"""
132 if not column in self.indexed_columns:
133 internal_name = self.orig_to_internal[column]
134 indexname = "Index_%s_%s" % (self.tablename, internal_name)
135 stmt = "CREATE INDEX %s ON %s (%s);" % (indexname, self.tablename,
136 internal_name)
137 self.db.execute(stmt)
138 self.indexed_columns[column] = 1
139
140 def field_count(self):
141 return len(self.columns)
142
143 def field_info(self, i):
144 col = self.columns[i]
145 return col.type, col.name, 0, 0
146
147 def field_info_by_name(self, name):
148 for col in self.columns:
149 if col.name == name:
150 return col.type, col.name, 0, 0
151 else:
152 return None
153
154 def record_count(self):
155 result = self.db.execute("SELECT count(*) FROM %s;" % self.tablename)
156 return int(result[0])
157
158 def read_record(self, index):
159 if self.read_record_cursor is None or index <self.read_record_last_row:
160 stmt = ("SELECT %s FROM %s;"
161 % (", ".join([c.internal_name for c in self.columns]),
162 self.tablename))
163 self.read_record_cursor = self.db.cursor()
164 self.read_record_cursor.execute(stmt)
165 self.read_record_last_row = -1
166 for i in range(index - self.read_record_last_row):
167 result = self.read_record_cursor.fetchone()
168 self.read_record_last_row = index
169 result = dict(zip(self.orig_names, result))
170 return result
171
172 def field_range(self, colname):
173 col = self.name_to_column[colname]
174 iname = col.internal_name
175 min, max = self.db.execute("SELECT min(%s), max(%s) FROM %s;"
176 % (iname, iname, self.tablename))
177 converter = type_converter_map[col.type]
178 return ((converter(min), None), (converter(max), None))
179
180 def GetUniqueValues(self, colname):
181 iname = self.orig_to_internal[colname]
182 cursor = self.db.cursor()
183 cursor.execute("SELECT %s FROM %s GROUP BY %s;"
184 % (iname, self.tablename, iname))
185 result = []
186 while 1:
187 row = cursor.fetchone()
188 if row is None:
189 break
190 result.append(row[0])
191 return result
192
193
194 class TransientTable(TransientTableBase):
195
196 """A Table in a transient DB that starts as the copy of a Thuban Table."""
197
198 def __init__(self, transient_db, table):
199 """Create a new table in the given transient DB as a copy of table
200
201 The table argument can be any object implementing the Table
202 interface.
203 """
204 TransientTableBase.__init__(self, transient_db)
205 self.create(table)
206
207 def create(self, table):
208 columns = []
209 for i in range(table.field_count()):
210 type, name = table.field_info(i)[:2]
211 columns.append(ColumnReference(name, type,
212 self.db.new_column_name()))
213 TransientTableBase.create(self, columns)
214
215 # copy the input table to the transient db
216 insert_template = "INSERT INTO %s (%s) VALUES (%s);" \
217 % (self.tablename,
218 ", ".join([col.internal_name
219 for col in self.columns]),
220 ", ".join(["%%(%s)s" % col.name
221 for col in self.columns]))
222 cursor = self.db.cursor()
223 for i in range(table.record_count()):
224 cursor.execute(insert_template, table.read_record(i))
225 self.db.conn.commit()
226
227
228
229 class TransientJoinedTable(TransientTableBase):
230
231 """A Table in the transient DB that contains a join of two tables"""
232
233 def __init__(self, transient_db, left_table, left_field,
234 right_table, right_field = None):
235 """Create a new table in the transient DB as a join of two tables.
236
237 Both input tables, left_table and right_table must have a
238 transient_table method that returns a table object for a table
239 in the trnsient database. The join is performed on the condition
240 that the value of the left_field column the the left table is
241 equal to the value of the right_field in the right_table.
242
243 The joined table contains all columns of the input tables with
244 one exception: Any column in the right_table with the same name
245 as one of the columns in the left_table will be omitted. This is
246 somewhat of an implementation detail, but is done so that the
247 column names of the joined table can be the same as the column
248 names of the input tables without having to create prefixes.
249 """
250 TransientTableBase.__init__(self, transient_db)
251 self.left_table = left_table.transient_table()
252 self.left_field = left_field
253 self.right_table = right_table.transient_table()
254 if right_field:
255 self.right_field = right_field
256 else:
257 self.right_field = self.left_field
258 self.create()
259
260 def create(self):
261 """Internal: Create the table with the joined data"""
262 self.tablename = self.db.new_table_name()
263
264 self.right_table.ensure_index(self.right_field)
265
266 # Coalesce the column information
267 visited = {}
268 columns = []
269 for col in self.left_table.columns + self.right_table.columns:
270 if col.name in visited:
271 continue
272 columns.append(col)
273 TransientTableBase.create(self, columns)
274
275 # Copy the joined data to the table.
276 internal_names = [col.internal_name for col in self.columns]
277 stmt = "INSERT INTO %s (%s) SELECT %s FROM %s JOIN %s ON %s = %s;" \
278 % (self.tablename,
279 ", ".join(internal_names),
280 ", ".join(internal_names),
281 self.left_table.tablename,
282 self.right_table.tablename,
283 self.orig_to_internal[self.left_field],
284 self.orig_to_internal[self.right_field])
285 self.db.execute(stmt)
286
287
288 class AutoTransientTable:
289
290 """Table that copies data to a transient table on demand.
291
292 The AutoTransientTable takes another table as input and copies data
293 to a table in a TransientDatabase instance on demand.
294 """
295
296 def __init__(self, transient_db, table):
297 self.transient_db = transient_db
298 self.table = table
299 self.t_table = None
300
301 def record_count(self):
302 """Return the number of records"""
303 return self.table.record_count()
304
305 def field_count(self):
306 """Return the number of fields in a record"""
307 return self.table.field_count()
308
309 def field_info(self, field):
310 """Return a tuple (type, name, width, prec) for the field no. field
311
312 type is the data type of the field, name the name, width the
313 field width in characters and prec the decimal precision.
314 """
315 info = self.table.field_info(field)
316 if info:
317 info = info[:2] + (0, 0)
318 return info
319
320 def field_info_by_name(self, fieldName):
321 info = self.table.field_info_by_name(fieldName)
322 if info:
323 info = info[:2] + (0, 0)
324 return info
325
326 def read_record(self, record):
327 """Return the record no. record as a dict mapping field names to values
328 """
329 if self.t_table is not None:
330 return self.t_table.read_record(record)
331 else:
332 return self.table.read_record(record)
333
334 def write_record(self, record, values):
335 raise NotImplementedError
336
337 def copy_to_transient(self):
338 """Internal: Create a transient table and copy the data into it"""
339 self.t_table = TransientTable(self.transient_db, self)
340
341 def transient_table(self):
342 """
343 Return a table whose underlying implementation is in the transient db
344 """
345 if self.t_table is None:
346 self.copy_to_transient()
347 return self.t_table
348
349 def field_range(self, colname):
350 if self.t_table is None:
351 self.copy_to_transient()
352 return self.t_table.field_range(colname)
353
354 def GetUniqueValues(self, colname):
355 if self.t_table is None:
356 self.copy_to_transient()
357 return self.t_table.GetUniqueValues(colname)

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26