table.py 41 KB

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