table.py 36 KB

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