table.py 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198
  1. # -*- coding: utf-8 -*-
  2. """
  3. Created on Wed Aug 8 15:29:21 2012
  4. @author: pietro
  5. """
  6. from __future__ import (nested_scopes, generators, division, absolute_import,
  7. with_statement, print_function, unicode_literals)
  8. import os
  9. import sys
  10. if sys.version_info.major == 3:
  11. long = int
  12. unicode = str
  13. import ctypes
  14. import numpy as np
  15. from sqlite3 import OperationalError
  16. try:
  17. from collections import OrderedDict
  18. except:
  19. from grass.pygrass.orderdict import OrderedDict
  20. import grass.lib.vector as libvect
  21. from grass.pygrass.gis import Mapset
  22. from grass.pygrass.errors import DBError
  23. from grass.pygrass.utils import table_exist
  24. from grass.script.db import db_table_in_vector
  25. from grass.script.core import warning
  26. from grass.script.utils import decode
  27. from grass.pygrass.vector import sql
  28. from grass.lib.ctypes_preamble import String
  29. # For test purposes
  30. test_vector_name = "table_doctest_map"
  31. DRIVERS = ('sqlite', 'pg')
  32. def get_path(path, vect_name=None):
  33. """Return the full path to the database; replacing environment variable
  34. with real values
  35. :param path: The path with substitutional parameter
  36. :param vect_name: The name of the vector map
  37. >>> from grass.script.core import gisenv
  38. >>> import os
  39. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db'
  40. >>> new_path = get_path(path)
  41. >>> new_path2 = os.path.join(gisenv()['GISDBASE'], gisenv()['LOCATION_NAME'],
  42. ... gisenv()['MAPSET'], 'sqlite', 'sqlite.db')
  43. >>> new_path.replace("//","/") == new_path2.replace("//","/")
  44. True
  45. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/vector/$MAP/sqlite.db'
  46. >>> new_path = get_path(path, "test")
  47. >>> new_path2 = os.path.join(gisenv()['GISDBASE'], gisenv()['LOCATION_NAME'],
  48. ... gisenv()['MAPSET'], 'vector', 'test', 'sqlite.db')
  49. >>> new_path.replace("//","/") == new_path2.replace("//","/")
  50. True
  51. """
  52. if "$" not in path:
  53. return path
  54. else:
  55. mapset = Mapset()
  56. path = path.replace('$GISDBASE', mapset.gisdbase)
  57. path = path.replace('$LOCATION_NAME', mapset.location)
  58. path = path.replace('$MAPSET', mapset.name)
  59. if vect_name is not None:
  60. if isinstance(vect_name, String):
  61. vect_name = vect_name.data
  62. path = path.replace('$MAP', vect_name)
  63. return path
  64. class Filters(object):
  65. """Help user to build a simple sql query.
  66. >>> filter = Filters('table')
  67. >>> filter.get_sql()
  68. u'SELECT * FROM table;'
  69. >>> filter.where("area<10000").get_sql()
  70. u'SELECT * FROM table WHERE area<10000;'
  71. >>> filter.select("cat", "area").get_sql()
  72. u'SELECT cat, area FROM table WHERE area<10000;'
  73. >>> filter.order_by("area").limit(10).get_sql()
  74. u'SELECT cat, area FROM table WHERE area<10000 ORDER BY area LIMIT 10;'
  75. """
  76. def __init__(self, tname):
  77. self.tname = tname
  78. self._select = None
  79. self._where = None
  80. self._orderby = None
  81. self._limit = None
  82. self._groupby = None
  83. def __repr__(self):
  84. return "Filters(%r)" % self.get_sql()
  85. def select(self, *args):
  86. """Create the select query"""
  87. cols = ', '.join(args) if args else '*'
  88. select = sql.SELECT[:-1]
  89. self._select = select.format(cols=cols, tname=self.tname)
  90. return self
  91. def where(self, condition):
  92. """Create the where condition
  93. :param condition: the condition of where statement, for example
  94. `cat = 1`
  95. :type condition: str
  96. """
  97. self._where = 'WHERE {condition}'.format(condition=condition)
  98. return self
  99. def order_by(self, *orderby):
  100. """Create the order by condition
  101. :param orderby: the name of column/s to order the result
  102. :type orderby: str
  103. """
  104. self._orderby = 'ORDER BY {orderby}'.format(orderby=', '.join(orderby))
  105. return self
  106. def limit(self, number):
  107. """Create the limit condition
  108. :param number: the number to limit the result
  109. :type number: int
  110. """
  111. if not isinstance(number, int):
  112. raise ValueError("Must be an integer.")
  113. else:
  114. self._limit = 'LIMIT {number}'.format(number=number)
  115. return self
  116. def group_by(self, *groupby):
  117. """Create the group by condition
  118. :param groupby: the name of column/s to group the result
  119. :type groupby: str, list
  120. """
  121. self._groupby = 'GROUP BY {groupby}'.format(groupby=', '.join(groupby))
  122. return self
  123. def get_sql(self):
  124. """Return the SQL query"""
  125. sql_list = list()
  126. if self._select is None:
  127. self.select()
  128. sql_list.append(self._select)
  129. if self._where is not None:
  130. sql_list.append(self._where)
  131. if self._groupby is not None:
  132. sql_list.append(self._groupby)
  133. if self._orderby is not None:
  134. sql_list.append(self._orderby)
  135. if self._limit is not None:
  136. sql_list.append(self._limit)
  137. return "%s;" % ' '.join(sql_list)
  138. def reset(self):
  139. """Clean internal variables"""
  140. self._select = None
  141. self._where = None
  142. self._orderby = None
  143. self._limit = None
  144. self._groupby = None
  145. class Columns(object):
  146. """Object to work with columns table.
  147. It is possible to instantiate a Columns object given the table name and
  148. the database connection.
  149. For a sqlite table:
  150. >>> import sqlite3
  151. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db'
  152. >>> cols_sqlite = Columns(test_vector_name,
  153. ... sqlite3.connect(get_path(path)))
  154. >>> cols_sqlite.tname
  155. u'table_doctest_map'
  156. For a postgreSQL table:
  157. >>> import psycopg2 as pg #doctest: +SKIP
  158. >>> cols_pg = Columns(test_vector_name,
  159. ... pg.connect('host=localhost dbname=grassdb')) #doctest: +SKIP
  160. >>> cols_pg.tname #doctest: +SKIP
  161. 'table_doctest_map' #doctest: +SKIP
  162. """
  163. def __init__(self, tname, connection, key='cat'):
  164. self.tname = tname
  165. self.conn = connection
  166. self.key = key
  167. self.odict = None
  168. self.update_odict()
  169. def __contains__(self, item):
  170. return item in self.names()
  171. def __repr__(self):
  172. return "Columns(%r)" % list(self.items())
  173. def __getitem__(self, key):
  174. return self.odict[key]
  175. def __setitem__(self, name, new_type):
  176. self.cast(name, new_type)
  177. self.update_odict(self)
  178. def __iter__(self):
  179. return self.odict.__iter__()
  180. def __len__(self):
  181. return self.odict.__len__()
  182. def __eq__(self, obj):
  183. """Return True if two table have the same columns.
  184. >>> import sqlite3
  185. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db'
  186. >>> connection = sqlite3.connect(get_path(path))
  187. >>> cols0 = Columns(test_vector_name, connection)
  188. >>> cols1 = Columns(test_vector_name, connection)
  189. >>> cols0 == cols1
  190. True
  191. """
  192. return obj.tname == self.tname and obj.odict == self.odict
  193. def __ne__(self, other):
  194. return not self == other
  195. # Restore Python 2 hashing beaviour on Python 3
  196. __hash__ = object.__hash__
  197. def is_pg(self):
  198. """Return True if is a psycopg connection.
  199. >>> import sqlite3
  200. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db'
  201. >>> cols_sqlite = Columns(test_vector_name,
  202. ... sqlite3.connect(get_path(path)))
  203. >>> cols_sqlite.is_pg()
  204. False
  205. >>> import psycopg2 as pg #doctest: +SKIP
  206. >>> cols_pg = Columns(test_vector_name,
  207. ... pg.connect('host=localhost dbname=grassdb')) #doctest: +SKIP
  208. >>> cols_pg.is_pg() #doctest: +SKIP
  209. True
  210. """
  211. return hasattr(self.conn, 'xid')
  212. def update_odict(self):
  213. """Read columns name and types from table and update the odict
  214. attribute.
  215. """
  216. if self.is_pg():
  217. # is a postgres connection
  218. cur = self.conn.cursor()
  219. cur.execute("SELECT oid,typname FROM pg_type")
  220. diz = dict(cur.fetchall())
  221. odict = OrderedDict()
  222. import psycopg2 as pg
  223. try:
  224. cur.execute(sql.SELECT.format(cols='*', tname=self.tname))
  225. descr = cur.description
  226. for column in descr:
  227. name, ctype = column[:2]
  228. odict[name] = diz[ctype]
  229. except pg.ProgrammingError:
  230. pass
  231. self.odict = odict
  232. else:
  233. # is a sqlite connection
  234. cur = self.conn.cursor()
  235. cur.execute(sql.PRAGMA.format(tname=self.tname))
  236. descr = cur.fetchall()
  237. odict = OrderedDict()
  238. for column in descr:
  239. name, ctype = column[1:3]
  240. odict[name] = ctype
  241. self.odict = odict
  242. values = ','.join(['?', ] * self.__len__())
  243. kv = ','.join(['%s=?' % k for k in self.odict.keys() if k != self.key])
  244. where = "%s=?" % self.key
  245. self.insert_str = sql.INSERT.format(tname=self.tname, values=values)
  246. self.update_str = sql.UPDATE_WHERE.format(tname=self.tname,
  247. values=kv, condition=where)
  248. def sql_descr(self, remove=None):
  249. """Return a string with description of columns.
  250. Remove it is used to remove a columns.
  251. >>> import sqlite3
  252. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db'
  253. >>> cols_sqlite = Columns(test_vector_name,
  254. ... sqlite3.connect(get_path(path)))
  255. >>> cols_sqlite.sql_descr() # doctest: +ELLIPSIS
  256. u'cat INTEGER, name varchar(50), value double precision'
  257. >>> import psycopg2 as pg # doctest: +SKIP
  258. >>> cols_pg = Columns(test_vector_name,
  259. ... pg.connect('host=localhost dbname=grassdb')) # doctest: +SKIP
  260. >>> cols_pg.sql_descr() # doctest: +ELLIPSIS +SKIP
  261. u'cat INTEGER, name varchar(50), value double precision'
  262. """
  263. if remove:
  264. return ', '.join(['%s %s' % (key, val) for key, val in self.items()
  265. if key != remove])
  266. else:
  267. return ', '.join(['%s %s' % (key, val)
  268. for key, val in self.items()])
  269. def types(self):
  270. """Return a list with the column types.
  271. >>> import sqlite3
  272. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db'
  273. >>> cols_sqlite = Columns(test_vector_name,
  274. ... sqlite3.connect(get_path(path)))
  275. >>> cols_sqlite.types() # doctest: +ELLIPSIS
  276. [u'INTEGER', u'varchar(50)', u'double precision']
  277. >>> import psycopg2 as pg # doctest: +SKIP
  278. >>> cols_pg = Columns(test_vector_name,
  279. ... pg.connect('host=localhost dbname=grassdb')) # doctest: +SKIP
  280. >>> cols_pg.types() # doctest: +ELLIPSIS +SKIP
  281. [u'INTEGER', u'varchar(50)', u'double precision']
  282. """
  283. return self.odict.values()
  284. def names(self, remove=None, unicod=True):
  285. """Return a list with the column names.
  286. Remove it is used to remove a columns.
  287. >>> import sqlite3
  288. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db'
  289. >>> cols_sqlite = Columns(test_vector_name,
  290. ... sqlite3.connect(get_path(path)))
  291. >>> cols_sqlite.names() # doctest: +ELLIPSIS
  292. [u'cat', u'name', u'value']
  293. >>> import psycopg2 as pg # doctest: +SKIP
  294. >>> cols_pg = Columns(test_vector_name, # doctest: +SKIP
  295. ... pg.connect('host=localhost dbname=grassdb'))
  296. >>> cols_pg.names() # doctest: +ELLIPSIS +SKIP
  297. [u'cat', u'name', u'value']
  298. """
  299. if remove:
  300. nams = self.odict.keys()
  301. nams.remove(remove)
  302. else:
  303. nams = self.odict.keys()
  304. if unicod:
  305. return nams
  306. else:
  307. return [str(name) for name in nams]
  308. def items(self):
  309. """Return a list of tuple with column name and column type.
  310. >>> import sqlite3
  311. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db'
  312. >>> cols_sqlite = Columns(test_vector_name,
  313. ... sqlite3.connect(get_path(path)))
  314. >>> cols_sqlite.items() # doctest: +ELLIPSIS
  315. [(u'cat', u'INTEGER'), (u'name', u'varchar(50)'), (u'value', u'double precision')]
  316. >>> import psycopg2 as pg # doctest: +SKIP
  317. >>> cols_pg = Columns(test_vector_name,
  318. ... pg.connect('host=localhost dbname=grassdb')) # doctest: +SKIP
  319. >>> cols_pg.items() # doctest: +ELLIPSIS +SKIP
  320. [(u'cat', u'INTEGER'), (u'name', u'varchar(50)'), (u'value', u'double precision')]
  321. """
  322. return self.odict.items()
  323. def add(self, col_name, col_type):
  324. """Add a new column to the table.
  325. :param col_name: the name of column to add
  326. :type col_name: str
  327. :param col_type: the tipe of column to add
  328. :type col_type: str
  329. >>> import sqlite3
  330. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db'
  331. >>> from grass.pygrass.utils import copy, remove
  332. >>> copy(test_vector_name,'mycensus','vect')
  333. >>> cols_sqlite = Columns('mycensus',
  334. ... sqlite3.connect(get_path(path)))
  335. >>> cols_sqlite.add(['n_pizza'], ['INT'])
  336. >>> 'n_pizza' in cols_sqlite
  337. True
  338. >>> import psycopg2 as pg # doctest: +SKIP
  339. >>> cols_pg = Columns('boundary_municp_pg',
  340. ... pg.connect('host=localhost dbname=grassdb')) #doctest: +SKIP
  341. >>> cols_pg.add('n_pizza', 'INT') # doctest: +SKIP
  342. >>> 'n_pizza' in cols_pg # doctest: +SKIP
  343. True
  344. >>> remove('mycensus', 'vect')
  345. """
  346. def check(col_type):
  347. """Check the column type if it is supported by GRASS
  348. :param col_type: the type of column
  349. :type col_type: str
  350. """
  351. valid_type = ('DOUBLE PRECISION', 'DOUBLE', 'INT', 'INTEGER',
  352. 'DATE', 'VARCHAR')
  353. col = col_type.upper()
  354. valid = [col.startswith(tp) for tp in valid_type]
  355. if not any(valid):
  356. str_err = ("Type: %r is not supported."
  357. "\nSupported types are: %s")
  358. raise TypeError(str_err % (col_type, ", ".join(valid_type)))
  359. return col_type
  360. col_type = ([check(col_type), ] if isinstance(col_type, (str, unicode))
  361. else [check(col) for col in col_type])
  362. col_name = ([col_name, ] if isinstance(col_name, (str, unicode))
  363. else col_name)
  364. sqlcode = [sql.ADD_COL.format(tname=self.tname, cname=cn, ctype=ct)
  365. for cn, ct in zip(col_name, col_type)]
  366. cur = self.conn.cursor()
  367. cur.executescript('\n'.join(sqlcode))
  368. self.conn.commit()
  369. cur.close()
  370. self.update_odict()
  371. def rename(self, old_name, new_name):
  372. """Rename a column of the table.
  373. :param old_name: the name of existing column
  374. :type old_name: str
  375. :param new_name: the name of new column
  376. :type new_name: str
  377. >>> import sqlite3
  378. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db'
  379. >>> from grass.pygrass.utils import copy, remove
  380. >>> copy(test_vector_name,'mycensus','vect')
  381. >>> cols_sqlite = Columns('mycensus',
  382. ... sqlite3.connect(get_path(path)))
  383. >>> cols_sqlite.add(['n_pizza'], ['INT'])
  384. >>> 'n_pizza' in cols_sqlite
  385. True
  386. >>> cols_sqlite.rename('n_pizza', 'n_pizzas') # doctest: +ELLIPSIS
  387. >>> 'n_pizza' in cols_sqlite
  388. False
  389. >>> 'n_pizzas' in cols_sqlite
  390. True
  391. >>> import psycopg2 as pg # doctest: +SKIP
  392. >>> cols_pg = Columns(test_vector_name,
  393. ... pg.connect('host=localhost dbname=grassdb')) # doctest: +SKIP
  394. >>> cols_pg.rename('n_pizza', 'n_pizzas') # doctest: +SKIP
  395. >>> 'n_pizza' in cols_pg # doctest: +SKIP
  396. False
  397. >>> 'n_pizzas' in cols_pg # doctest: +SKIP
  398. True
  399. >>> remove('mycensus', 'vect')
  400. """
  401. cur = self.conn.cursor()
  402. if self.is_pg():
  403. cur.execute(sql.RENAME_COL.format(tname=self.tname,
  404. old_name=old_name,
  405. new_name=new_name))
  406. self.conn.commit()
  407. cur.close()
  408. self.update_odict()
  409. else:
  410. cur.execute(sql.ADD_COL.format(tname=self.tname,
  411. cname=new_name,
  412. ctype=str(self.odict[old_name])))
  413. cur.execute(sql.UPDATE.format(tname=self.tname,
  414. new_col=new_name,
  415. old_col=old_name))
  416. self.conn.commit()
  417. cur.close()
  418. self.update_odict()
  419. self.drop(old_name)
  420. def cast(self, col_name, new_type):
  421. """Change the column type.
  422. :param col_name: the name of column
  423. :type col_name: str
  424. :param new_type: the new type of column
  425. :type new_type: str
  426. >>> import sqlite3
  427. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db'
  428. >>> from grass.pygrass.utils import copy, remove
  429. >>> copy(test_vector_name,'mycensus','vect')
  430. >>> cols_sqlite = Columns('mycensus',
  431. ... sqlite3.connect(get_path(path)))
  432. >>> cols_sqlite.add(['n_pizzas'], ['INT'])
  433. >>> cols_sqlite.cast('n_pizzas', 'float8') # doctest: +ELLIPSIS
  434. Traceback (most recent call last):
  435. ...
  436. DBError: SQLite does not support to cast columns.
  437. >>> import psycopg2 as pg # doctest: +SKIP
  438. >>> cols_pg = Columns(test_vector_name,
  439. ... pg.connect('host=localhost dbname=grassdb')) # doctest: +SKIP
  440. >>> cols_pg.cast('n_pizzas', 'float8') # doctest: +SKIP
  441. >>> cols_pg['n_pizzas'] # doctest: +SKIP
  442. 'float8'
  443. >>> remove('mycensus', 'vect')
  444. .. warning ::
  445. It is not possible to cast a column with sqlite
  446. """
  447. if self.is_pg():
  448. cur = self.conn.cursor()
  449. cur.execute(sql.CAST_COL.format(tname=self.tname, col=col_name,
  450. ctype=new_type))
  451. self.conn.commit()
  452. cur.close()
  453. self.update_odict()
  454. else:
  455. # sqlite does not support rename columns:
  456. raise DBError('SQLite does not support to cast columns.')
  457. def drop(self, col_name):
  458. """Drop a column from the table.
  459. :param col_name: the name of column to remove
  460. :type col_name: str
  461. >>> import sqlite3
  462. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db'
  463. >>> from grass.pygrass.utils import copy, remove
  464. >>> copy(test_vector_name,'mycensus','vect')
  465. >>> cols_sqlite = Columns('mycensus',
  466. ... sqlite3.connect(get_path(path)))
  467. >>> cols_sqlite.drop('name') # doctest: +ELLIPSIS
  468. >>> 'name' in cols_sqlite
  469. False
  470. >>> import psycopg2 as pg # doctest: +SKIP
  471. >>> cols_pg = Columns(test_vector_name,
  472. ... pg.connect('host=localhost dbname=grassdb')) # doctest: +SKIP
  473. >>> cols_pg.drop('name') # doctest: +SKIP
  474. >>> 'name' in cols_pg # doctest: +SKIP
  475. False
  476. >>> remove('mycensus','vect')
  477. """
  478. cur = self.conn.cursor()
  479. if self.is_pg():
  480. cur.execute(sql.DROP_COL.format(tname=self.tname,
  481. cname=col_name))
  482. else:
  483. desc = str(self.sql_descr(remove=col_name))
  484. names = ', '.join(self.names(remove=col_name, unicod=False))
  485. queries = sql.DROP_COL_SQLITE.format(tname=self.tname,
  486. keycol=self.key,
  487. coldef=desc,
  488. colnames=names).split('\n')
  489. for query in queries:
  490. cur.execute(query)
  491. self.conn.commit()
  492. cur.close()
  493. self.update_odict()
  494. class Link(object):
  495. """Define a Link between vector map and the attributes table.
  496. It is possible to define a Link object or given all the information
  497. (layer, name, table name, key, database, driver):
  498. >>> link = Link(1, 'link0', test_vector_name, 'cat',
  499. ... '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db', 'sqlite')
  500. >>> link.layer
  501. 1
  502. >>> link.name
  503. 'link0'
  504. >>> link.table_name
  505. 'table_doctest_map'
  506. >>> link.key
  507. 'cat'
  508. >>> link.database
  509. '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db'
  510. >>> link.driver
  511. 'sqlite'
  512. >>> link
  513. Link(1, link0, sqlite)
  514. It is possible to change parameters with:
  515. >>> link.driver = 'pg' # doctest: +SKIP
  516. >>> link.driver # doctest: +SKIP
  517. 'pg'
  518. >>> link.driver = 'postgres' # doctest: +ELLIPSIS +SKIP
  519. Traceback (most recent call last):
  520. ...
  521. TypeError: Driver not supported, use: sqlite, pg.
  522. >>> link.driver # doctest: +SKIP
  523. 'pg'
  524. >>> link.number = 0 # doctest: +ELLIPSIS +SKIP
  525. Traceback (most recent call last):
  526. ...
  527. TypeError: Number must be positive and greater than 0.
  528. Or given a c_fieldinfo object that is a ctypes pointer to the field_info C
  529. struct. ::
  530. >>> link = Link(c_fieldinfo = ctypes.pointer(libvect.field_info()))
  531. """
  532. def _get_layer(self):
  533. return self.c_fieldinfo.contents.number
  534. def _set_layer(self, number):
  535. if number <= 0:
  536. raise TypeError("Number must be positive and greater than 0.")
  537. self.c_fieldinfo.contents.number = number
  538. layer = property(fget=_get_layer, fset=_set_layer,
  539. doc="Set and obtain layer number")
  540. def _get_name(self):
  541. return self.c_fieldinfo.contents.name
  542. def _set_name(self, name):
  543. self.c_fieldinfo.contents.name = String(name)
  544. name = property(fget=_get_name, fset=_set_name,
  545. doc="Set and obtain name vale")
  546. def _get_table(self):
  547. return self.c_fieldinfo.contents.table
  548. def _set_table(self, new_name):
  549. self.c_fieldinfo.contents.table = String(new_name)
  550. table_name = property(fget=_get_table, fset=_set_table,
  551. doc="Set and obtain table name value")
  552. def _get_key(self):
  553. return self.c_fieldinfo.contents.key
  554. def _set_key(self, key):
  555. self.c_fieldinfo.contents.key = String(key)
  556. key = property(fget=_get_key, fset=_set_key,
  557. doc="Set and obtain cat value")
  558. def _get_database(self):
  559. return self.c_fieldinfo.contents.database
  560. def _set_database(self, database):
  561. self.c_fieldinfo.contents.database = String(database)
  562. database = property(fget=_get_database, fset=_set_database,
  563. doc="Set and obtain database value")
  564. def _get_driver(self):
  565. return self.c_fieldinfo.contents.driver
  566. def _set_driver(self, driver):
  567. if driver not in ('sqlite', 'pg'):
  568. str_err = "Driver not supported, use: %s." % ", ".join(DRIVERS)
  569. raise TypeError(str_err)
  570. self.c_fieldinfo.contents.driver = String(driver)
  571. driver = property(fget=_get_driver, fset=_set_driver,
  572. doc="Set and obtain driver value. The drivers supported \
  573. by PyGRASS are: SQLite and PostgreSQL")
  574. def __init__(self, layer=1, name=None, table=None, key='cat',
  575. database='$GISDBASE/$LOCATION_NAME/'
  576. '$MAPSET/sqlite/sqlite.db',
  577. driver='sqlite', c_fieldinfo=None):
  578. if c_fieldinfo is not None:
  579. self.c_fieldinfo = c_fieldinfo
  580. else:
  581. self.c_fieldinfo = ctypes.pointer(libvect.field_info())
  582. self.layer = layer
  583. self.name = name
  584. self.table_name = table
  585. self.key = key
  586. self.database = database
  587. self.driver = driver
  588. def __repr__(self):
  589. return "Link(%d, %s, %s)" % (self.layer, self.name, self.driver)
  590. def __eq__(self, link):
  591. """Return True if two Link instance have the same parameters.
  592. >>> l0 = Link(1, 'link0', test_vector_name, 'cat',
  593. ... '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db', 'sqlite')
  594. >>> l1 = Link(1, 'link0', test_vector_name, 'cat',
  595. ... '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db', 'sqlite')
  596. >>> l2 = Link(2, 'link0', test_vector_name, 'cat',
  597. ... '$GISDBASE/$LOCATION_NAME/PERMANENT/sqlite/sqlite.db', 'sqlite')
  598. >>> l0 == l1
  599. True
  600. >>> l1 == l2
  601. False
  602. """
  603. attrs = ['layer', 'name', 'table_name', 'key', 'driver']
  604. for attr in attrs:
  605. if getattr(self, attr) != getattr(link, attr):
  606. return False
  607. return True
  608. def __ne__(self, other):
  609. return not self == other
  610. # Restore Python 2 hashing beaviour on Python 3
  611. __hash__ = object.__hash__
  612. def connection(self):
  613. """Return a connection object.
  614. >>> link = Link(1, 'link0', test_vector_name, 'cat',
  615. ... '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db',
  616. ... 'sqlite')
  617. >>> conn = link.connection()
  618. >>> cur = conn.cursor()
  619. >>> link.table_name
  620. 'table_doctest_map'
  621. >>> cur.execute("SELECT cat, name, value from %s" %
  622. ... link.table_name) # doctest: +ELLIPSIS
  623. <sqlite3.Cursor object at ...>
  624. >>> cur.fetchone() #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
  625. (1, u'point', 1.0)
  626. >>> cur.close()
  627. >>> conn.close()
  628. """
  629. driver = self.driver
  630. if isinstance(driver, String):
  631. driver = decode(driver.data)
  632. if driver == 'sqlite':
  633. import sqlite3
  634. # Numpy is using some custom integer data types to efficiently
  635. # pack data into memory. Since these types aren't familiar to
  636. # sqlite, you'll have to tell it about how to handle them.
  637. for t in (np.int8, np.int16, np.int32, np.int64, np.uint8,
  638. np.uint16, np.uint32, np.uint64):
  639. sqlite3.register_adapter(t, long)
  640. dbpath = get_path(self.database, self.table_name)
  641. dbdirpath = os.path.split(dbpath)[0]
  642. if isinstance(dbpath, String):
  643. dbpath = dbpath.data
  644. if isinstance(dbdirpath, String):
  645. dbdirpath = dbdirpath.data
  646. if not os.path.exists(dbdirpath):
  647. os.mkdir(dbdirpath)
  648. return sqlite3.connect(dbpath)
  649. elif driver == 'pg':
  650. try:
  651. import psycopg2
  652. psycopg2.paramstyle = 'qmark'
  653. db = ' '.join(self.database.split(','))
  654. return psycopg2.connect(db)
  655. except ImportError:
  656. er = "You need to install psycopg2 to connect with this table."
  657. raise ImportError(er)
  658. else:
  659. str_err = "Driver is not supported yet, pleas use: sqlite or pg"
  660. raise TypeError(str_err)
  661. def table(self):
  662. """Return a Table object.
  663. >>> link = Link(1, 'link0', test_vector_name, 'cat',
  664. ... '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db',
  665. ... 'sqlite')
  666. >>> table = link.table()
  667. >>> table.filters.select('cat', 'name', 'value')
  668. Filters(u'SELECT cat, name, value FROM table_doctest_map;')
  669. >>> cur = table.execute()
  670. >>> cur.fetchone()
  671. (1, u'point', 1.0)
  672. >>> cur.close()
  673. """
  674. return Table(self.table_name, self.connection(), self.key)
  675. def info(self):
  676. """Print information of the link.
  677. >>> link = Link(1, 'link0', test_vector_name, 'cat',
  678. ... '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db',
  679. ... 'sqlite')
  680. >>> link.info()
  681. layer: 1
  682. name: link0
  683. table: table_doctest_map
  684. key: cat
  685. database: $GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db
  686. driver: sqlite
  687. """
  688. print("layer: ", self.layer)
  689. print("name: ", self.name)
  690. print("table: ", self.table_name)
  691. print("key: ", self.key)
  692. print("database: ", self.database)
  693. print("driver: ", self.driver)
  694. class DBlinks(object):
  695. """Interface containing link to the table DB.
  696. >>> from grass.pygrass.vector import VectorTopo
  697. >>> cens = VectorTopo(test_vector_name)
  698. >>> cens.open(mode='r')
  699. >>> dblinks = DBlinks(cens.c_mapinfo)
  700. >>> dblinks
  701. DBlinks([Link(1, table_doctest_map, sqlite)])
  702. >>> dblinks[0]
  703. Link(1, table_doctest_map, sqlite)
  704. >>> dblinks[test_vector_name]
  705. Link(1, table_doctest_map, sqlite)
  706. >>> cens.close()
  707. """
  708. def __init__(self, c_mapinfo):
  709. self.c_mapinfo = c_mapinfo
  710. def __len__(self):
  711. return self.num_dblinks()
  712. def __iter__(self):
  713. return (self.by_index(i)
  714. for i in range(self.num_dblinks()))
  715. def __getitem__(self, item):
  716. """
  717. """
  718. if isinstance(item, int):
  719. return self.by_index(item)
  720. else:
  721. return self.by_name(item)
  722. def __repr__(self):
  723. return "DBlinks(%r)" % [link for link in self.__iter__()]
  724. def by_index(self, indx):
  725. """Return a Link object by index
  726. :param indx: the index where add new point
  727. :type indx: int
  728. """
  729. nlinks = self.num_dblinks()
  730. if nlinks == 0:
  731. raise IndexError
  732. if indx < 0:
  733. indx += nlinks
  734. if indx > nlinks:
  735. raise IndexError
  736. c_fieldinfo = libvect.Vect_get_dblink(self.c_mapinfo, indx)
  737. return Link(c_fieldinfo=c_fieldinfo) if c_fieldinfo else None
  738. def by_layer(self, layer):
  739. """Return the chosen Link using the layer
  740. :param layer: the number of layer
  741. :type layer: int
  742. """
  743. c_fieldinfo = libvect.Vect_get_field(self.c_mapinfo, layer)
  744. return Link(c_fieldinfo=c_fieldinfo) if c_fieldinfo else None
  745. def by_name(self, name):
  746. """Return the chosen Link using the name
  747. :param name: the name of Link
  748. :type name: str
  749. """
  750. c_fieldinfo = libvect.Vect_get_field_by_name(self.c_mapinfo, name)
  751. return Link(c_fieldinfo=c_fieldinfo) if c_fieldinfo else None
  752. def num_dblinks(self):
  753. """Return the number of DBlinks"""
  754. return libvect.Vect_get_num_dblinks(self.c_mapinfo)
  755. def add(self, link):
  756. """Add a new link. Need to open vector map in write mode
  757. :param link: the Link to add to the DBlinks
  758. :type link: a Link object
  759. >>> from grass.pygrass.vector import VectorTopo
  760. >>> test_vect = VectorTopo(test_vector_name)
  761. >>> test_vect.open(mode='r')
  762. >>> dblinks = DBlinks(test_vect.c_mapinfo)
  763. >>> dblinks
  764. DBlinks([Link(1, table_doctest_map, sqlite)])
  765. >>> link = Link(2, 'pg_link', test_vector_name, 'cat',
  766. ... 'host=localhost dbname=grassdb', 'pg') # doctest: +SKIP
  767. >>> dblinks.add(link) # doctest: +SKIP
  768. >>> dblinks # doctest: +SKIP
  769. DBlinks([Link(1, table_doctest_map, sqlite)])
  770. """
  771. #TODO: check if open in write mode or not.
  772. libvect.Vect_map_add_dblink(self.c_mapinfo,
  773. link.layer, link.name, link.table_name,
  774. link.key, link.database, link.driver)
  775. def remove(self, key, force=False):
  776. """Remove a link. If force set to true remove also the table
  777. :param key: the key of Link
  778. :type key: str
  779. :param force: if True remove also the table from database otherwise
  780. only the link between table and vector
  781. :type force: boole
  782. >>> from grass.pygrass.vector import VectorTopo
  783. >>> test_vect = VectorTopo(test_vector_name)
  784. >>> test_vect.open(mode='r')
  785. >>> dblinks = DBlinks(test_vect.c_mapinfo)
  786. >>> dblinks
  787. DBlinks([Link(1, table_doctest_map, sqlite)])
  788. >>> dblinks.remove('pg_link') # doctest: +SKIP
  789. >>> dblinks # need to open vector map in write mode
  790. DBlinks([Link(1, table_doctest_map, sqlite)])
  791. """
  792. if force:
  793. link = self.by_name(key)
  794. table = link.table()
  795. table.drop(force=force)
  796. if isinstance(key, unicode):
  797. key = self.from_name_to_num(key)
  798. libvect.Vect_map_del_dblink(self.c_mapinfo, key)
  799. def from_name_to_num(self, name):
  800. """
  801. Vect_get_field_number
  802. """
  803. return libvect.Vect_get_field_number(self.c_mapinfo, name)
  804. class Table(object):
  805. """
  806. >>> import sqlite3
  807. >>> path = '$GISDBASE/$LOCATION_NAME/PERMANENT/sqlite/sqlite.db'
  808. >>> tab_sqlite = Table(name=test_vector_name,
  809. ... connection=sqlite3.connect(get_path(path)))
  810. >>> tab_sqlite.name
  811. u'table_doctest_map'
  812. >>> import psycopg2 # doctest: +SKIP
  813. >>> tab_pg = Table(test_vector_name,
  814. ... psycopg2.connect('host=localhost dbname=grassdb',
  815. ... 'pg')) # doctest: +SKIP
  816. >>> tab_pg.columns # doctest: +ELLIPSIS +SKIP
  817. Columns([('cat', 'int4'), ...])
  818. """
  819. def _get_name(self):
  820. """Private method to return the name of table"""
  821. return self._name
  822. def _set_name(self, new_name):
  823. """Private method to set the name of table
  824. :param new_name: the new name of table
  825. :type new_name: str
  826. """
  827. old_name = self._name
  828. cur = self.conn.cursor()
  829. cur.execute(sql.RENAME_TAB.format(old_name=old_name,
  830. new_name=new_name))
  831. self.conn.commit()
  832. cur.close()
  833. name = property(fget=_get_name, fset=_set_name,
  834. doc="Set and obtain table name")
  835. def __init__(self, name, connection, key='cat'):
  836. self._name = name
  837. self.conn = connection
  838. self.key = key
  839. self.columns = Columns(self.name,
  840. self.conn,
  841. self.key)
  842. self.filters = Filters(self.name)
  843. def __repr__(self):
  844. """
  845. >>> import sqlite3
  846. >>> path = '$GISDBASE/$LOCATION_NAME/PERMANENT/sqlite/sqlite.db'
  847. >>> tab_sqlite = Table(name=test_vector_name,
  848. ... connection=sqlite3.connect(get_path(path)))
  849. >>> tab_sqlite
  850. Table(u'table_doctest_map')
  851. """
  852. return "Table(%r)" % (self.name)
  853. def __iter__(self):
  854. cur = self.execute()
  855. return (cur.fetchone() for _ in range(self.__len__()))
  856. def __len__(self):
  857. """Return the number of rows"""
  858. return self.n_rows()
  859. def drop(self, cursor=None, force=False):
  860. """Method to drop table from database
  861. :param cursor: the cursor to connect, if None it use the cursor
  862. of connection table object
  863. :type cursor: Cursor object
  864. :param force: True to remove the table, by default False to print
  865. advice
  866. :type force: bool
  867. """
  868. cur = cursor if cursor else self.conn.cursor()
  869. if self.exist(cursor=cur):
  870. used = db_table_in_vector(self.name)
  871. if used is not None and len(used) > 0 and not force:
  872. print(_("Deleting table <%s> which is attached"
  873. " to following map(s):") % self.name)
  874. for vect in used:
  875. warning("%s" % vect)
  876. print(_("You must use the force flag to actually"
  877. " remove it. Exiting."))
  878. else:
  879. cur.execute(sql.DROP_TAB.format(tname=self.name))
  880. def n_rows(self):
  881. """Return the number of rows
  882. >>> import sqlite3
  883. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db'
  884. >>> tab_sqlite = Table(name=test_vector_name,
  885. ... connection=sqlite3.connect(get_path(path)))
  886. >>> tab_sqlite.n_rows()
  887. 3
  888. """
  889. cur = self.conn.cursor()
  890. cur.execute(sql.SELECT.format(cols='Count(*)', tname=self.name))
  891. number = cur.fetchone()[0]
  892. cur.close()
  893. return number
  894. def execute(self, sql_code=None, cursor=None, many=False, values=None):
  895. """Execute SQL code from a given string or build with filters and
  896. return a cursor object.
  897. :param sql_code: the SQL code to execute, if not pass it use filters
  898. variable
  899. :type sql_code: str
  900. :param cursor: the cursor to connect, if None it use the cursor
  901. of connection table object
  902. :type cursor: Cursor object
  903. :param many: True to run executemany function
  904. :type many: bool
  905. :param values: The values to substitute into sql_code string
  906. :type values: list of tuple
  907. >>> import sqlite3
  908. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db'
  909. >>> tab_sqlite = Table(name=test_vector_name,
  910. ... connection=sqlite3.connect(get_path(path)))
  911. >>> tab_sqlite.filters.select('cat', 'name').order_by('value')
  912. Filters(u'SELECT cat, name FROM table_doctest_map ORDER BY value;')
  913. >>> cur = tab_sqlite.execute()
  914. >>> cur.fetchone() #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
  915. (1, u'point')
  916. """
  917. try:
  918. sqlc = sql_code if sql_code else self.filters.get_sql()
  919. cur = cursor if cursor else self.conn.cursor()
  920. if many and values:
  921. return cur.executemany(sqlc, values)
  922. return cur.execute(sqlc, values) if values else cur.execute(sqlc)
  923. except Exception as exc:
  924. raise ValueError("The SQL statement is not correct:\n%r,\n"
  925. "values: %r,\n"
  926. "SQL error: %s" % (sqlc, values, str(exc)))
  927. def exist(self, cursor=None):
  928. """Return True if the table already exist in the DB, False otherwise
  929. :param cursor: the cursor to connect, if None it use the cursor
  930. of connection table object
  931. """
  932. cur = cursor if cursor else self.conn.cursor()
  933. return table_exist(cur, self.name)
  934. def insert(self, values, cursor=None, many=False):
  935. """Insert a new row
  936. :param values: a tuple of values to insert, it is possible to insert
  937. more rows using a list of tuple and parameter `many`
  938. :type values: tuple
  939. :param cursor: the cursor to connect, if None it use the cursor
  940. of connection table object
  941. :type cursor: Cursor object
  942. :param many: True to run executemany function
  943. :type many: bool
  944. """
  945. cur = cursor if cursor else self.conn.cursor()
  946. if many:
  947. return cur.executemany(self.columns.insert_str, values)
  948. return cur.execute(self.columns.insert_str, values)
  949. def update(self, key, values, cursor=None):
  950. """Update a table row
  951. :param key: the rowid
  952. :type key: int
  953. :param values: the values to insert without row id.
  954. For example if we have a table with four columns:
  955. cat, c0, c1, c2 the values list should
  956. containing only c0, c1, c2 values.
  957. :type values: list
  958. :param cursor: the cursor to connect, if None it use the cursor
  959. of connection table object
  960. :type cursor: Cursor object
  961. """
  962. cur = cursor if cursor else self.conn.cursor()
  963. vals = list(values) + [key, ]
  964. return cur.execute(self.columns.update_str, vals)
  965. def create(self, cols, name=None, overwrite=False, cursor=None):
  966. """Create a new table
  967. :param cols:
  968. :type cols:
  969. :param name: the name of table to create, None for the name of Table object
  970. :type name: str
  971. :param overwrite: overwrite existing table
  972. :type overwrite: bool
  973. :param cursor: the cursor to connect, if None it use the cursor
  974. of connection table object
  975. :type cursor: Cursor object
  976. """
  977. cur = cursor if cursor else self.conn.cursor()
  978. coldef = ',\n'.join(['%s %s' % col for col in cols])
  979. if name:
  980. newname = name
  981. else:
  982. newname = self.name
  983. try:
  984. cur.execute(sql.CREATE_TAB.format(tname=newname, coldef=coldef))
  985. self.conn.commit()
  986. except OperationalError: # OperationalError
  987. if overwrite:
  988. self.drop(force=True)
  989. cur.execute(sql.CREATE_TAB.format(tname=newname,
  990. coldef=coldef))
  991. self.conn.commit()
  992. else:
  993. print("The table: %s already exist." % self.name)
  994. cur.close()
  995. self.columns.update_odict()
  996. if __name__ == "__main__":
  997. import doctest
  998. from grass.pygrass import utils
  999. utils.create_test_vector_map(test_vector_name)
  1000. doctest.testmod()
  1001. """Remove the generated vector map, if exist"""
  1002. from grass.pygrass.utils import get_mapset_vector
  1003. from grass.script.core import run_command
  1004. mset = get_mapset_vector(test_vector_name, mapset='')
  1005. if mset:
  1006. run_command("g.remove", flags='f', type='vector', name=test_vector_name)