32 |
return psycopg.__version__ |
return psycopg.__version__ |
33 |
|
|
34 |
if psycopg is not None: |
if psycopg is not None: |
35 |
|
# type_map maps psycopg type objects. It's a list of pairs since |
36 |
|
# the psycopg type objects are unhashable. |
37 |
type_map = [(psycopg.STRING, table.FIELDTYPE_STRING), |
type_map = [(psycopg.STRING, table.FIELDTYPE_STRING), |
38 |
(psycopg.INTEGER, table.FIELDTYPE_INT), |
(psycopg.INTEGER, table.FIELDTYPE_INT), |
39 |
|
(psycopg.ROWID, table.FIELDTYPE_INT), |
40 |
(psycopg.FLOAT, table.FIELDTYPE_DOUBLE)] |
(psycopg.FLOAT, table.FIELDTYPE_DOUBLE)] |
41 |
|
|
42 |
|
# _raw_type_map maps the postgresql type constants to Thuban type |
43 |
|
# constants. This is very low level and postgresql specific and |
44 |
|
# should be used only when necessary. |
45 |
|
_raw_type_map = {} |
46 |
|
def _fill_raw_type_map(): |
47 |
|
for psycopg_type, thuban_type in type_map: |
48 |
|
for value in psycopg_type.values: |
49 |
|
_raw_type_map[value] = thuban_type |
50 |
|
_fill_raw_type_map() |
51 |
|
|
52 |
|
|
53 |
def quote_identifier(ident): |
def quote_identifier(ident): |
54 |
"""Return a quoted version of the identifier ident. |
"""Return a quoted version of the identifier ident. |
148 |
|
|
149 |
def GeometryTables(self): |
def GeometryTables(self): |
150 |
"""Return a list with the names of all tables with a geometry column""" |
"""Return a list with the names of all tables with a geometry column""" |
151 |
|
|
152 |
|
# The query is basically taken from the psql v. 7.2.1. When |
153 |
|
# started with -E it prints the queries used for internal |
154 |
|
# commands such as \d, which does mostly what we need here. |
155 |
cursor = self.connection.cursor() |
cursor = self.connection.cursor() |
156 |
cursor.execute("SELECT f_table_name FROM geometry_columns;") |
cursor.execute("SELECT c.relname FROM pg_class c" |
157 |
|
" WHERE c.relkind IN ('r', 'v')" |
158 |
|
# Omit the system tables |
159 |
|
" AND c.relname !~ '^pg_'" |
160 |
|
# Omit the special PostGIS tables |
161 |
|
" AND c.relname NOT IN ('geometry_columns'," |
162 |
|
" 'spatial_ref_sys')" |
163 |
|
" AND %d in (SELECT a.atttypid FROM pg_attribute a" |
164 |
|
" WHERE a.attrelid = c.oid)" |
165 |
|
" ORDER BY c.relname;", (self.geometry_type,)) |
166 |
result = [row[0] for row in cursor.fetchall()] |
result = [row[0] for row in cursor.fetchall()] |
167 |
self.connection.commit() |
self.connection.commit() |
168 |
return result |
return result |
169 |
|
|
170 |
|
def table_columns(self, tablename): |
171 |
|
"""Experimental: return information about the columns of a table |
172 |
|
|
173 |
|
Return value is a list of (name, type) pairs where name is the |
174 |
|
name of the column and type either one of the field type columns |
175 |
|
or the string 'geometry' indicating a geometry column. |
176 |
|
|
177 |
|
The intended use of this method is for table selection dialogs |
178 |
|
which need to determine which columns are usable as id or |
179 |
|
geometry columns respectively. Suitable id columns will have |
180 |
|
type FIELDTYPE_INT and geometry columns will have 'geometry'. |
181 |
|
""" |
182 |
|
result = [] |
183 |
|
cursor = self.connection.cursor() |
184 |
|
|
185 |
|
# This query is taken basically from the \d command of psql |
186 |
|
# 7.2.1 |
187 |
|
cursor.execute("SELECT a.attname, a.atttypid, a.attnum" |
188 |
|
" FROM pg_class c, pg_attribute a" |
189 |
|
" WHERE c.relname = %s AND a.attrelid = c.oid" |
190 |
|
" ORDER BY a.attnum;", (tablename,)) |
191 |
|
|
192 |
|
for row in cursor.fetchall(): |
193 |
|
col_name, col_type, col_attnum = row |
194 |
|
col = None |
195 |
|
if col_attnum < 1: |
196 |
|
# It's a system column. Only the OID is interesting |
197 |
|
# here |
198 |
|
if col_name == "oid": |
199 |
|
col = (col_name, _raw_type_map[col_type]) |
200 |
|
else: |
201 |
|
# If it's an integer |
202 |
|
thuban_type = _raw_type_map.get(col_type) |
203 |
|
if thuban_type is not None: |
204 |
|
col = (col_name, thuban_type) |
205 |
|
elif row[1] == self.geometry_type: |
206 |
|
col = (col_name, "geometry") |
207 |
|
if col is not None: |
208 |
|
result.append(col) |
209 |
|
|
210 |
|
return result |
211 |
|
|
212 |
def cursor(self): |
def cursor(self): |
213 |
"""Return a DB API 2.0 cursor for the database""" |
"""Return a DB API 2.0 cursor for the database""" |
214 |
return self.connection.cursor() |
return self.connection.cursor() |
243 |
descriptions returned by Columns() and other methods. |
descriptions returned by Columns() and other methods. |
244 |
""" |
""" |
245 |
|
|
246 |
def __init__(self, db, tablename, id_column = "gid"): |
def __init__(self, db, tablename, id_column = None): |
247 |
"""Initialize the PostGISTable. |
"""Initialize the PostGISTable. |
248 |
|
|
249 |
The db parameter should be an instance of PostGISConnection and |
The db parameter should be an instance of PostGISConnection and |
252 |
The id_column parameter should be the name of a column in the |
The id_column parameter should be the name of a column in the |
253 |
table that can be used to identify rows. The column must have |
table that can be used to identify rows. The column must have |
254 |
the type integer and be unique and not null. |
the type integer and be unique and not null. |
255 |
|
|
256 |
|
For backwards compatibility reasons, the id_column parameter is |
257 |
|
optional. If not given the table must have a column called |
258 |
|
'gid' which is used as the id_column. New code should always |
259 |
|
provide this parameter. |
260 |
""" |
""" |
261 |
self.db = db |
self.db = db |
262 |
self.tablename = tablename |
self.tablename = tablename |
263 |
# Tablename quoted for use in SQL statements. |
# Tablename quoted for use in SQL statements. |
264 |
self.quoted_tablename = quote_identifier(tablename) |
self.quoted_tablename = quote_identifier(tablename) |
265 |
|
|
266 |
|
if not id_column: |
267 |
|
id_column = "gid" |
268 |
self.id_column = id_column |
self.id_column = id_column |
269 |
# id column name quoted for use in SQL statements. |
# id column name quoted for use in SQL statements. |
270 |
self.quoted_id_column = quote_identifier(id_column) |
self.quoted_id_column = quote_identifier(id_column) |
319 |
"""Return the dbconnection used by the table""" |
"""Return the dbconnection used by the table""" |
320 |
return self.db |
return self.db |
321 |
|
|
322 |
|
def IDColumn(self): |
323 |
|
"""Return the column description object for the id column. |
324 |
|
|
325 |
|
If the oid column was used as the id column, the return value is |
326 |
|
not one of the regular column objects that would be returned by |
327 |
|
e.g. the Column() method, but it still has meaningful name |
328 |
|
attribute. |
329 |
|
""" |
330 |
|
if self.id_column == "oid": |
331 |
|
return PostGISColumn(self.id_column, table.FIELDTYPE_INT, None) |
332 |
|
return self.column_map[self.id_column] |
333 |
|
|
334 |
def TableName(self): |
def TableName(self): |
335 |
"""Return the name of the table in the database""" |
"""Return the name of the table in the database""" |
336 |
return self.tablename |
return self.tablename |
514 |
self.quoted_geo_col = quote_identifier(self.geometry_column) |
self.quoted_geo_col = quote_identifier(self.geometry_column) |
515 |
|
|
516 |
def _fetch_table_information(self): |
def _fetch_table_information(self): |
517 |
"""Extend inherited method to retrieve the SRID""" |
"""Extend inherited method to retrieve the SRID and shape type""" |
518 |
PostGISTable._fetch_table_information(self) |
PostGISTable._fetch_table_information(self) |
519 |
|
|
520 |
|
# First, try to get it from the geometry_columns table. |
521 |
cursor = self.db.cursor() |
cursor = self.db.cursor() |
522 |
cursor.execute("SELECT srid FROM geometry_columns" |
cursor.execute("SELECT srid, type FROM geometry_columns" |
523 |
" WHERE f_table_name = %s AND f_geometry_column=%s", |
" WHERE f_table_name = %s AND f_geometry_column=%s", |
524 |
(self.tablename, self.geometry_column)) |
(self.tablename, self.geometry_column)) |
525 |
self.srid = cursor.fetchone()[0] |
row = cursor.fetchone() |
526 |
|
if row is not None: |
527 |
|
self.srid = row[0] |
528 |
|
self.shape_type = shapetype_map.get(row[1]) |
529 |
|
return |
530 |
|
|
531 |
|
# The table is probably really a view and thus not in |
532 |
|
# geometry_columns. Use a different approach |
533 |
|
cursor = self.db.cursor() |
534 |
|
cursor.execute("SELECT DISTINCT SRID(%s) FROM %s;" % |
535 |
|
(quote_identifier(self.geometry_column), |
536 |
|
self.tablename)) |
537 |
|
row = cursor.fetchone() |
538 |
|
if row is not None: |
539 |
|
self.srid = row[0] |
540 |
|
# Try to see whether there's another one |
541 |
|
row = cursor.fetchone() |
542 |
|
if row is not None: |
543 |
|
# There are at least two different srids. We don't |
544 |
|
# support that |
545 |
|
self.srid = None |
546 |
|
|
547 |
|
cursor = self.db.cursor() |
548 |
|
cursor.execute("SELECT DISTINCT GeometryType(%s) FROM %s;" |
549 |
|
% (quote_identifier(self.geometry_column), |
550 |
|
self.tablename)) |
551 |
|
row = cursor.fetchone() |
552 |
|
if row is not None: |
553 |
|
self.shape_type = shapetype_map.get(row[0]) |
554 |
|
# Try to see whether there's another one |
555 |
|
row = cursor.fetchone() |
556 |
|
if row is not None: |
557 |
|
# There are at least two different srids. We don't |
558 |
|
# support that |
559 |
|
self.shape_type = None |
560 |
|
|
561 |
def _create_col_from_description(self, index, description): |
def _create_col_from_description(self, index, description): |
562 |
"""Extend the inherited method to find geometry columns |
"""Extend the inherited method to find geometry columns |
598 |
""" |
""" |
599 |
return None |
return None |
600 |
|
|
601 |
|
def GeometryColumn(self): |
602 |
|
"""Return the column description object for the geometry column |
603 |
|
|
604 |
|
There's currently no FIELDTYPE constant for this column, so the |
605 |
|
return value is not a regular column object that could also be |
606 |
|
returned from e.g. the Column() method. Only the name attribute |
607 |
|
of the return value is meaningful at the moment. |
608 |
|
""" |
609 |
|
return PostGISColumn(self.geometry_column, None, None) |
610 |
|
|
611 |
def ShapeType(self): |
def ShapeType(self): |
612 |
"""Return the type of the shapes in the shapestore.""" |
"""Return the type of the shapes in the shapestore.""" |
613 |
cursor = self.db.cursor() |
return self.shape_type |
|
cursor.execute("SELECT type FROM geometry_columns WHERE" |
|
|
" f_table_name=%s AND f_geometry_column=%s", |
|
|
(self.tablename, self.geometry_column)) |
|
|
result = cursor.fetchone()[0] |
|
|
cursor.close() |
|
|
return shapetype_map[result] |
|
614 |
|
|
615 |
def RawShapeFormat(self): |
def RawShapeFormat(self): |
616 |
"""Return the raw data format of the shape data. |
"""Return the raw data format of the shape data. |