table.py 42 KB

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