table.py 41 KB

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