1 |
# Copyright (C) 2003, 2004 by Intevation GmbH |
# Copyright (C) 2003, 2004, 2005 by Intevation GmbH |
2 |
# Authors: |
# Authors: |
3 |
# Martin Mueller <[email protected]> |
# Martin Mueller <[email protected]> |
4 |
# Bernhard Herzog <[email protected]> |
# Bernhard Herzog <[email protected]> |
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. |
107 |
except psycopg.OperationalError, val: |
except psycopg.OperationalError, val: |
108 |
raise ConnectionError(str(val)) |
raise ConnectionError(str(val)) |
109 |
|
|
110 |
|
# Use autocommit mode. For simple reading of the database it's |
111 |
|
# sufficient and we don't have to care much about error |
112 |
|
# handling. Without autocommit, an errors during a cursor's |
113 |
|
# execute method requires a rollback on the connection, |
114 |
|
# otherwise later queries with the same or other cursors sharing |
115 |
|
# the same connection will lead to further errors ("ERROR: |
116 |
|
# current transaction is aborted, commands ignored until end of |
117 |
|
# transaction block") |
118 |
|
self.connection.autocommit() |
119 |
|
|
120 |
# determine the OID for the geometry type. This is PostGIS |
# determine the OID for the geometry type. This is PostGIS |
121 |
# specific. |
# specific. |
122 |
cursor = self.connection.cursor() |
cursor = self.connection.cursor() |
158 |
|
|
159 |
def GeometryTables(self): |
def GeometryTables(self): |
160 |
"""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""" |
161 |
|
|
162 |
|
# The query is basically taken from the psql v. 7.2.1. When |
163 |
|
# started with -E it prints the queries used for internal |
164 |
|
# commands such as \d, which does mostly what we need here. |
165 |
cursor = self.connection.cursor() |
cursor = self.connection.cursor() |
166 |
cursor.execute("SELECT f_table_name FROM geometry_columns;") |
cursor.execute("SELECT c.relname FROM pg_class c" |
167 |
|
" WHERE c.relkind IN ('r', 'v')" |
168 |
|
# Omit the system tables |
169 |
|
" AND c.relname !~ '^pg_'" |
170 |
|
# Omit the special PostGIS tables |
171 |
|
" AND c.relname NOT IN ('geometry_columns'," |
172 |
|
" 'spatial_ref_sys')" |
173 |
|
" AND %d in (SELECT a.atttypid FROM pg_attribute a" |
174 |
|
" WHERE a.attrelid = c.oid)" |
175 |
|
" ORDER BY c.relname;", (self.geometry_type,)) |
176 |
result = [row[0] for row in cursor.fetchall()] |
result = [row[0] for row in cursor.fetchall()] |
177 |
self.connection.commit() |
self.connection.commit() |
178 |
return result |
return result |
179 |
|
|
180 |
|
def table_columns(self, tablename): |
181 |
|
"""Experimental: return information about the columns of a table |
182 |
|
|
183 |
|
Return value is a list of (name, type) pairs where name is the |
184 |
|
name of the column and type either one of the field type columns |
185 |
|
or the string 'geometry' indicating a geometry column. |
186 |
|
|
187 |
|
The intended use of this method is for table selection dialogs |
188 |
|
which need to determine which columns are usable as id or |
189 |
|
geometry columns respectively. Suitable id columns will have |
190 |
|
type FIELDTYPE_INT and geometry columns will have 'geometry'. |
191 |
|
""" |
192 |
|
result = [] |
193 |
|
cursor = self.connection.cursor() |
194 |
|
|
195 |
|
# This query is taken basically from the \d command of psql |
196 |
|
# 7.2.1 |
197 |
|
cursor.execute("SELECT a.attname, a.atttypid, a.attnum" |
198 |
|
" FROM pg_class c, pg_attribute a" |
199 |
|
" WHERE c.relname = %s AND a.attrelid = c.oid" |
200 |
|
" ORDER BY a.attnum;", (tablename,)) |
201 |
|
|
202 |
|
for row in cursor.fetchall(): |
203 |
|
col_name, col_type, col_attnum = row |
204 |
|
col = None |
205 |
|
if col_attnum < 1: |
206 |
|
# It's a system column. Only the OID is interesting |
207 |
|
# here |
208 |
|
if col_name == "oid": |
209 |
|
col = (col_name, _raw_type_map[col_type]) |
210 |
|
else: |
211 |
|
# If it's an integer |
212 |
|
thuban_type = _raw_type_map.get(col_type) |
213 |
|
if thuban_type is not None: |
214 |
|
col = (col_name, thuban_type) |
215 |
|
elif row[1] == self.geometry_type: |
216 |
|
col = (col_name, "geometry") |
217 |
|
if col is not None: |
218 |
|
result.append(col) |
219 |
|
|
220 |
|
return result |
221 |
|
|
222 |
def cursor(self): |
def cursor(self): |
223 |
"""Return a DB API 2.0 cursor for the database""" |
"""Return a DB API 2.0 cursor for the database""" |
224 |
return self.connection.cursor() |
return self.connection.cursor() |
253 |
descriptions returned by Columns() and other methods. |
descriptions returned by Columns() and other methods. |
254 |
""" |
""" |
255 |
|
|
256 |
def __init__(self, db, tablename): |
def __init__(self, db, tablename, id_column = None): |
257 |
"""Initialize the PostGISTable. |
"""Initialize the PostGISTable. |
258 |
|
|
259 |
The db parameter should be an instance of PostGISConnection and |
The db parameter should be an instance of PostGISConnection and |
260 |
tablename the name of a table in the database represented by db. |
tablename the name of a table in the database represented by db. |
261 |
|
|
262 |
|
The id_column parameter should be the name of a column in the |
263 |
|
table that can be used to identify rows. The column must have |
264 |
|
the type integer and be unique and not null. |
265 |
|
|
266 |
|
For backwards compatibility reasons, the id_column parameter is |
267 |
|
optional. If not given the table must have a column called |
268 |
|
'gid' which is used as the id_column. New code should always |
269 |
|
provide this parameter. |
270 |
""" |
""" |
271 |
self.db = db |
self.db = db |
272 |
self.tablename = tablename |
self.tablename = tablename |
273 |
# Tablename quoted for use in SQL statements. |
# Tablename quoted for use in SQL statements. |
274 |
self.quoted_tablename = quote_identifier(tablename) |
self.quoted_tablename = quote_identifier(tablename) |
275 |
|
|
276 |
|
if not id_column: |
277 |
|
id_column = "gid" |
278 |
|
self.id_column = id_column |
279 |
|
# id column name quoted for use in SQL statements. |
280 |
|
self.quoted_id_column = quote_identifier(id_column) |
281 |
|
|
282 |
# Map column names and indices to column objects. |
# Map column names and indices to column objects. |
283 |
self.column_map = {} |
self.column_map = {} |
284 |
|
|
292 |
description = cursor.description |
description = cursor.description |
293 |
|
|
294 |
for i in range(len(description)): |
for i in range(len(description)): |
295 |
for pgtyp, tabletyp in type_map: |
col = self._create_col_from_description(i, description[i]) |
296 |
if pgtyp == description[i][1]: |
if col is not None: |
297 |
col = PostGISColumn(description[i][0], tabletyp, |
self.columns.append(col) |
|
len(self.columns)) |
|
|
break |
|
|
else: |
|
|
if description[i][1] == self.db.geometry_type: |
|
|
self.geometry_column = description[i][0] |
|
|
self.quoted_geo_col =quote_identifier(self.geometry_column) |
|
|
# No matching table type. Ignore the column. |
|
|
# FIXME: We should at least print a warning about |
|
|
# ignored columns |
|
|
continue |
|
|
self.columns.append(col) |
|
298 |
|
|
299 |
for col in self.columns: |
for col in self.columns: |
300 |
self.column_map[col.name] = col |
self.column_map[col.name] = col |
306 |
for col in self.columns]), |
for col in self.columns]), |
307 |
self.quoted_tablename)) |
self.quoted_tablename)) |
308 |
|
|
309 |
|
def _create_col_from_description(self, index, description): |
310 |
|
"""Return the column object for the column described by description |
311 |
|
|
312 |
|
The parameter index is the index of the column. The description |
313 |
|
is a sequence taken from the cursor's description attribute for |
314 |
|
the column. That means description[0] is the name of the column |
315 |
|
and description[1] the type. |
316 |
|
|
317 |
|
Return None if the column can't be represented for some reason, |
318 |
|
e.g. because its type is not yet supported or needs to be |
319 |
|
treated in some special way. Derived classes may extend this |
320 |
|
method. |
321 |
|
""" |
322 |
|
for pgtyp, tabletyp in type_map: |
323 |
|
if pgtyp == description[1]: |
324 |
|
return PostGISColumn(description[0], tabletyp, |
325 |
|
len(self.columns)) |
326 |
|
return None |
327 |
|
|
328 |
def DBConnection(self): |
def DBConnection(self): |
329 |
"""Return the dbconnection used by the table""" |
"""Return the dbconnection used by the table""" |
330 |
return self.db |
return self.db |
331 |
|
|
332 |
|
def IDColumn(self): |
333 |
|
"""Return the column description object for the id column. |
334 |
|
|
335 |
|
If the oid column was used as the id column, the return value is |
336 |
|
not one of the regular column objects that would be returned by |
337 |
|
e.g. the Column() method, but it still has meaningful name |
338 |
|
attribute. |
339 |
|
""" |
340 |
|
if self.id_column == "oid": |
341 |
|
return PostGISColumn(self.id_column, table.FIELDTYPE_INT, None) |
342 |
|
return self.column_map[self.id_column] |
343 |
|
|
344 |
def TableName(self): |
def TableName(self): |
345 |
"""Return the name of the table in the database""" |
"""Return the name of the table in the database""" |
346 |
return self.tablename |
return self.tablename |
377 |
def RowIdToOrdinal(self, gid): |
def RowIdToOrdinal(self, gid): |
378 |
"""Return the row ordinal given its id""" |
"""Return the row ordinal given its id""" |
379 |
cursor = self.db.cursor() |
cursor = self.db.cursor() |
380 |
cursor.execute("SELECT count(*) FROM %s WHERE gid < %d;" |
cursor.execute("SELECT count(*) FROM %s WHERE %s < %d;" |
381 |
% (self.quoted_tablename, gid)) |
% (self.quoted_tablename, self.quoted_id_column, gid)) |
382 |
return cursor.fetchone()[0] |
return cursor.fetchone()[0] |
383 |
|
|
384 |
def RowOrdinalToId(self, num): |
def RowOrdinalToId(self, num): |
385 |
"""Return the rowid for given its ordinal""" |
"""Return the rowid for given its ordinal""" |
386 |
cursor = self.db.cursor() |
cursor = self.db.cursor() |
387 |
cursor.execute("SELECT gid FROM %s LIMIT 1 OFFSET %d;" |
cursor.execute("SELECT %s FROM %s LIMIT 1 OFFSET %d;" |
388 |
% (self.quoted_tablename, num)) |
% (self.quoted_id_column, self.quoted_tablename, num)) |
389 |
return cursor.fetchone()[0] |
return cursor.fetchone()[0] |
390 |
|
|
391 |
def ReadRowAsDict(self, row, row_is_ordinal = 0): |
def ReadRowAsDict(self, row, row_is_ordinal = 0): |
393 |
if row_is_ordinal: |
if row_is_ordinal: |
394 |
stmt = self.query_stmt + " LIMIT 1 OFFSET %d" % row |
stmt = self.query_stmt + " LIMIT 1 OFFSET %d" % row |
395 |
else: |
else: |
396 |
stmt = self.query_stmt + " WHERE gid = %d" % row |
stmt = self.query_stmt + " WHERE %s = %d" % (self.quoted_id_column, |
397 |
|
row) |
398 |
cursor.execute(stmt) |
cursor.execute(stmt) |
399 |
result = {} |
result = {} |
400 |
for col, value in zip(self.columns, cursor.fetchone()): |
for col, value in zip(self.columns, cursor.fetchone()): |
408 |
(self.column_map[col].quoted_name, self.quoted_tablename, |
(self.column_map[col].quoted_name, self.quoted_tablename, |
409 |
row)) |
row)) |
410 |
else: |
else: |
411 |
stmt = ("SELECT %s FROM %s WHERE gid = %d" % |
stmt = ("SELECT %s FROM %s WHERE %s = %d" % |
412 |
(self.column_map[col].quoted_name, self.quoted_tablename, |
(self.column_map[col].quoted_name, self.quoted_tablename, |
413 |
row)) |
self.quoted_id_column, row)) |
414 |
cursor.execute(stmt) |
cursor.execute(stmt) |
415 |
return cursor.fetchone()[0] |
return cursor.fetchone()[0] |
416 |
|
|
442 |
right_template = "%s" |
right_template = "%s" |
443 |
params = (right,) |
params = (right,) |
444 |
|
|
445 |
query = "SELECT gid FROM %s WHERE %s %s %s ORDER BY gid;" \ |
query = "SELECT %s FROM %s WHERE %s %s %s ORDER BY %s;" \ |
446 |
% (self.quoted_tablename, left.quoted_name, comparison, |
% (self.quoted_id_column, self.quoted_tablename, |
447 |
right_template) |
left.quoted_name, comparison, right_template, |
448 |
|
self.quoted_id_column) |
449 |
|
|
450 |
cursor = self.db.cursor() |
cursor = self.db.cursor() |
451 |
cursor.execute(query, params) |
cursor.execute(query, params) |
488 |
|
|
489 |
shapetype_map = {"POLYGON": SHAPETYPE_POLYGON, |
shapetype_map = {"POLYGON": SHAPETYPE_POLYGON, |
490 |
"MULTIPOLYGON": SHAPETYPE_POLYGON, |
"MULTIPOLYGON": SHAPETYPE_POLYGON, |
491 |
|
"LINESTRING": SHAPETYPE_ARC, |
492 |
"MULTILINESTRING": SHAPETYPE_ARC, |
"MULTILINESTRING": SHAPETYPE_ARC, |
493 |
"POINT": SHAPETYPE_POINT} |
"POINT": SHAPETYPE_POINT} |
494 |
|
|
497 |
|
|
498 |
"""Shapestore interface to a table in a PostGIS database""" |
"""Shapestore interface to a table in a PostGIS database""" |
499 |
|
|
500 |
|
def __init__(self, db, tablename, id_column = "gid", |
501 |
|
geometry_column = None): |
502 |
|
"""Initialize the PostGISShapeStore. |
503 |
|
|
504 |
|
The db parameter should be an instance of PostGISConnection and |
505 |
|
tablename the name of a table in the database represented by db. |
506 |
|
|
507 |
|
The id_column parameter should be the name of a column in the |
508 |
|
table that can be used to identify rows. The column must have |
509 |
|
the type integer and be unique and not null. |
510 |
|
|
511 |
|
The geometry_column paramter, if given, should be the name of |
512 |
|
the geometry column to use. If the name given is not a geometry |
513 |
|
column, raise a ValueError. |
514 |
|
|
515 |
|
If no geometry_column is given, the table must have exactly one |
516 |
|
geometry column. If it has more than one and the |
517 |
|
geometry_column is not given, a ValueError will be raised. |
518 |
|
""" |
519 |
|
self.geometry_column = geometry_column |
520 |
|
self.geometry_column_was_given = geometry_column is not None |
521 |
|
PostGISTable.__init__(self, db, tablename, id_column) |
522 |
|
|
523 |
|
# For convenience, we have a quoted version of the geometry |
524 |
|
# column in self.quoted_geo_col |
525 |
|
self.quoted_geo_col = quote_identifier(self.geometry_column) |
526 |
|
|
527 |
def _fetch_table_information(self): |
def _fetch_table_information(self): |
528 |
"""Extend inherited method to retrieve the SRID""" |
"""Extend inherited method to retrieve the SRID and shape type""" |
529 |
PostGISTable._fetch_table_information(self) |
PostGISTable._fetch_table_information(self) |
530 |
|
|
531 |
|
# First, try to get it from the geometry_columns table. |
532 |
|
cursor = self.db.cursor() |
533 |
|
cursor.execute("SELECT srid, type FROM geometry_columns" |
534 |
|
" WHERE f_table_name = %s AND f_geometry_column=%s", |
535 |
|
(self.tablename, self.geometry_column)) |
536 |
|
row = cursor.fetchone() |
537 |
|
if row is not None: |
538 |
|
self.srid = row[0] |
539 |
|
self.shape_type = shapetype_map.get(row[1]) |
540 |
|
return |
541 |
|
|
542 |
|
# The table is probably really a view and thus not in |
543 |
|
# geometry_columns. Use a different approach |
544 |
|
cursor = self.db.cursor() |
545 |
|
cursor.execute("SELECT DISTINCT SRID(%s) FROM %s;" % |
546 |
|
(quote_identifier(self.geometry_column), |
547 |
|
self.tablename)) |
548 |
|
row = cursor.fetchone() |
549 |
|
if row is not None: |
550 |
|
self.srid = row[0] |
551 |
|
# Try to see whether there's another one |
552 |
|
row = cursor.fetchone() |
553 |
|
if row is not None: |
554 |
|
# There are at least two different srids. We don't |
555 |
|
# support that |
556 |
|
self.srid = None |
557 |
|
|
558 |
cursor = self.db.cursor() |
cursor = self.db.cursor() |
559 |
cursor.execute("SELECT srid FROM geometry_columns" |
cursor.execute("SELECT DISTINCT GeometryType(%s) FROM %s;" |
560 |
" WHERE f_table_name = %s", (self.tablename,)) |
% (quote_identifier(self.geometry_column), |
561 |
self.srid = cursor.fetchone()[0] |
self.tablename)) |
562 |
|
row = cursor.fetchone() |
563 |
|
if row is not None: |
564 |
|
self.shape_type = shapetype_map.get(row[0]) |
565 |
|
# Try to see whether there's another one |
566 |
|
row = cursor.fetchone() |
567 |
|
if row is not None: |
568 |
|
# There are at least two different srids. We don't |
569 |
|
# support that |
570 |
|
self.shape_type = None |
571 |
|
|
572 |
|
def _create_col_from_description(self, index, description): |
573 |
|
"""Extend the inherited method to find geometry columns |
574 |
|
|
575 |
|
If the column indicated by the parameters is a geometry column, |
576 |
|
record its name in self.geometry_column and a quoted version in |
577 |
|
self.quoted_geo_col. In any case return the return value of the |
578 |
|
inherited method. |
579 |
|
""" |
580 |
|
col = PostGISTable._create_col_from_description(self, index, |
581 |
|
description) |
582 |
|
col_name, col_type = description[:2] |
583 |
|
if self.geometry_column_was_given: |
584 |
|
if (col_name == self.geometry_column |
585 |
|
and col_type != self.db.geometry_type): |
586 |
|
raise TypeError("Column %s in %s is not a geometry column" |
587 |
|
% (self.geometry_column, self.tablename)) |
588 |
|
else: |
589 |
|
if col is None: |
590 |
|
if description[1] == self.db.geometry_type: |
591 |
|
# The column is a geometry column. If the name of |
592 |
|
# the geometry column was not given to the |
593 |
|
# constructor, and we encounter two geometry |
594 |
|
# columns, raise a value error |
595 |
|
if self.geometry_column is None: |
596 |
|
self.geometry_column = description[0] |
597 |
|
else: |
598 |
|
raise TypeError("Table %s has two geometry columns" |
599 |
|
" and no column name was given" |
600 |
|
% (self.tablename,)) |
601 |
|
return col |
602 |
|
|
603 |
def Table(self): |
def Table(self): |
604 |
"""Return self since a PostGISShapeStore is its own table.""" |
"""Return self since a PostGISShapeStore is its own table.""" |
609 |
""" |
""" |
610 |
return None |
return None |
611 |
|
|
612 |
|
def GeometryColumn(self): |
613 |
|
"""Return the column description object for the geometry column |
614 |
|
|
615 |
|
There's currently no FIELDTYPE constant for this column, so the |
616 |
|
return value is not a regular column object that could also be |
617 |
|
returned from e.g. the Column() method. Only the name attribute |
618 |
|
of the return value is meaningful at the moment. |
619 |
|
""" |
620 |
|
return PostGISColumn(self.geometry_column, None, None) |
621 |
|
|
622 |
def ShapeType(self): |
def ShapeType(self): |
623 |
"""Return the type of the shapes in the shapestore.""" |
"""Return the type of the shapes in the shapestore.""" |
624 |
cursor = self.db.cursor() |
return self.shape_type |
|
cursor.execute("SELECT type FROM geometry_columns WHERE" |
|
|
" f_table_name=%s", (self.tablename,)) |
|
|
result = cursor.fetchone()[0] |
|
|
cursor.close() |
|
|
return shapetype_map[result] |
|
625 |
|
|
626 |
def RawShapeFormat(self): |
def RawShapeFormat(self): |
627 |
"""Return the raw data format of the shape data. |
"""Return the raw data format of the shape data. |
658 |
|
|
659 |
def Shape(self, shapeid): |
def Shape(self, shapeid): |
660 |
cursor = self.db.cursor() |
cursor = self.db.cursor() |
661 |
cursor.execute("SELECT AsText(%s) FROM %s WHERE gid=%d" |
cursor.execute("SELECT AsText(%s) FROM %s WHERE %s=%d" |
662 |
% (self.quoted_geo_col, self.quoted_tablename, shapeid)) |
% (self.quoted_geo_col, self.quoted_tablename, |
663 |
|
self.quoted_id_column, shapeid)) |
664 |
wkt = cursor.fetchone()[0] |
wkt = cursor.fetchone()[0] |
665 |
cursor.close() |
cursor.close() |
666 |
return PostGISShape(shapeid, wkt) |
return PostGISShape(shapeid, wkt) |
667 |
|
|
668 |
def AllShapes(self): |
def AllShapes(self): |
669 |
cursor = self.db.cursor() |
cursor = self.db.cursor() |
670 |
cursor.execute("SELECT gid, AsText(%s) FROM %s ORDER BY gid" |
cursor.execute("SELECT %s, AsText(%s) FROM %s ORDER BY %s" |
671 |
% (self.quoted_geo_col, self.quoted_tablename)) |
% (self.quoted_id_column, self.quoted_geo_col, |
672 |
|
self.quoted_tablename, self.quoted_id_column)) |
673 |
while 1: |
while 1: |
674 |
result = cursor.fetchone() |
result = cursor.fetchone() |
675 |
if result is None: |
if result is None: |
685 |
% (left, bottom, left, top, right, top, right, bottom, |
% (left, bottom, left, top, right, top, right, bottom, |
686 |
left, bottom)) |
left, bottom)) |
687 |
cursor = self.db.cursor() |
cursor = self.db.cursor() |
688 |
cursor.execute("SELECT gid, AsText(%s) FROM %s" |
cursor.execute("SELECT %(gid)s, AsText(%(geom)s) FROM %(table)s" |
689 |
" WHERE %s && GeometryFromText('%s', %d) ORDER BY gid" |
" WHERE %(geom)s && GeometryFromText('%(box)s', %(srid)d)" |
690 |
% (self.quoted_geo_col, self.quoted_tablename, |
" ORDER BY %(gid)s" |
691 |
self.quoted_geo_col, geom, self.srid)) |
% {"table": self.quoted_tablename, |
692 |
|
"geom": self.quoted_geo_col, |
693 |
|
"gid": self.quoted_id_column, |
694 |
|
"box": geom, |
695 |
|
"srid": self.srid}) |
696 |
while 1: |
while 1: |
697 |
result = cursor.fetchone() |
result = cursor.fetchone() |
698 |
if result is None: |
if result is None: |