table.py 41 KB

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