table.py 40 KB

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