table.py 42 KB

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