table.py 41 KB

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