table.py 42 KB

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