table.py 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254
  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. if isinstance(item, int):
  757. return self.by_index(item)
  758. else:
  759. return self.by_name(item)
  760. def __repr__(self):
  761. return "DBlinks(%r)" % [link for link in self.__iter__()]
  762. def by_index(self, indx):
  763. """Return a Link object by index
  764. :param indx: the index where add new point
  765. :type indx: int
  766. """
  767. nlinks = self.num_dblinks()
  768. if nlinks == 0:
  769. raise IndexError
  770. if indx < 0:
  771. indx += nlinks
  772. if indx > nlinks:
  773. raise IndexError
  774. c_fieldinfo = libvect.Vect_get_dblink(self.c_mapinfo, indx)
  775. return Link(c_fieldinfo=c_fieldinfo) if c_fieldinfo else None
  776. def by_layer(self, layer):
  777. """Return the chosen Link using the layer
  778. :param layer: the number of layer
  779. :type layer: int
  780. """
  781. c_fieldinfo = libvect.Vect_get_field(self.c_mapinfo, layer)
  782. return Link(c_fieldinfo=c_fieldinfo) if c_fieldinfo else None
  783. def by_name(self, name):
  784. """Return the chosen Link using the name
  785. :param name: the name of Link
  786. :type name: str
  787. """
  788. c_fieldinfo = libvect.Vect_get_field_by_name(self.c_mapinfo, name)
  789. return Link(c_fieldinfo=c_fieldinfo) if c_fieldinfo else None
  790. def num_dblinks(self):
  791. """Return the number of DBlinks"""
  792. return libvect.Vect_get_num_dblinks(self.c_mapinfo)
  793. def add(self, link):
  794. """Add a new link. Need to open vector map in write mode
  795. :param link: the Link to add to the DBlinks
  796. :type link: a Link object
  797. >>> from grass.pygrass.vector import VectorTopo
  798. >>> test_vect = VectorTopo(test_vector_name)
  799. >>> test_vect.open(mode='r')
  800. >>> dblinks = DBlinks(test_vect.c_mapinfo)
  801. >>> dblinks
  802. DBlinks([Link(1, table_doctest_map, sqlite)])
  803. >>> link = Link(2, 'pg_link', test_vector_name, 'cat',
  804. ... 'host=localhost dbname=grassdb', 'pg') # doctest: +SKIP
  805. >>> dblinks.add(link) # doctest: +SKIP
  806. >>> dblinks # doctest: +SKIP
  807. DBlinks([Link(1, table_doctest_map, sqlite)])
  808. """
  809. # TODO: check if open in write mode or not.
  810. libvect.Vect_map_add_dblink(
  811. self.c_mapinfo,
  812. link.layer,
  813. link.name,
  814. link.table_name,
  815. link.key,
  816. link.database,
  817. link.driver,
  818. )
  819. def remove(self, key, force=False):
  820. """Remove a link. If force set to true remove also the table
  821. :param key: the key of Link
  822. :type key: str
  823. :param force: if True remove also the table from database otherwise
  824. only the link between table and vector
  825. :type force: boole
  826. >>> from grass.pygrass.vector import VectorTopo
  827. >>> test_vect = VectorTopo(test_vector_name)
  828. >>> test_vect.open(mode='r')
  829. >>> dblinks = DBlinks(test_vect.c_mapinfo)
  830. >>> dblinks
  831. DBlinks([Link(1, table_doctest_map, sqlite)])
  832. >>> dblinks.remove('pg_link') # doctest: +SKIP
  833. >>> dblinks # need to open vector map in write mode
  834. DBlinks([Link(1, table_doctest_map, sqlite)])
  835. """
  836. if force:
  837. link = self.by_name(key)
  838. table = link.table()
  839. table.drop(force=force)
  840. if isinstance(key, unicode):
  841. key = self.from_name_to_num(key)
  842. libvect.Vect_map_del_dblink(self.c_mapinfo, key)
  843. def from_name_to_num(self, name):
  844. """
  845. Vect_get_field_number
  846. """
  847. return libvect.Vect_get_field_number(self.c_mapinfo, name)
  848. class Table(object):
  849. """
  850. >>> import sqlite3
  851. >>> path = '$GISDBASE/$LOCATION_NAME/PERMANENT/sqlite/sqlite.db'
  852. >>> tab_sqlite = Table(name=test_vector_name,
  853. ... connection=sqlite3.connect(get_path(path)))
  854. >>> tab_sqlite.name
  855. 'table_doctest_map'
  856. >>> import psycopg2 # doctest: +SKIP
  857. >>> tab_pg = Table(test_vector_name,
  858. ... psycopg2.connect('host=localhost dbname=grassdb',
  859. ... 'pg')) # doctest: +SKIP
  860. >>> tab_pg.columns # doctest: +ELLIPSIS +SKIP
  861. Columns([('cat', 'int4'), ...])
  862. """
  863. def _get_name(self):
  864. """Private method to return the name of table"""
  865. return self._name
  866. def _set_name(self, new_name):
  867. """Private method to set the name of table
  868. :param new_name: the new name of table
  869. :type new_name: str
  870. """
  871. old_name = self._name
  872. cur = self.conn.cursor()
  873. cur.execute(sql.RENAME_TAB.format(old_name=old_name, new_name=new_name))
  874. self.conn.commit()
  875. cur.close()
  876. name = property(fget=_get_name, fset=_set_name, doc="Set and obtain table name")
  877. def __init__(self, name, connection, key="cat"):
  878. self._name = name
  879. self.conn = connection
  880. self.key = key
  881. self.columns = Columns(self.name, self.conn, self.key)
  882. self.filters = Filters(self.name)
  883. def __repr__(self):
  884. """
  885. >>> import sqlite3
  886. >>> path = '$GISDBASE/$LOCATION_NAME/PERMANENT/sqlite/sqlite.db'
  887. >>> tab_sqlite = Table(name=test_vector_name,
  888. ... connection=sqlite3.connect(get_path(path)))
  889. >>> tab_sqlite
  890. Table('table_doctest_map')
  891. """
  892. return "Table(%r)" % (self.name)
  893. def __iter__(self):
  894. cur = self.execute()
  895. return (cur.fetchone() for _ in range(self.__len__()))
  896. def __len__(self):
  897. """Return the number of rows"""
  898. return self.n_rows()
  899. def drop(self, cursor=None, force=False):
  900. """Method to drop table from database
  901. :param cursor: the cursor to connect, if None it use the cursor
  902. of connection table object
  903. :type cursor: Cursor object
  904. :param force: True to remove the table, by default False to print
  905. advice
  906. :type force: bool
  907. """
  908. cur = cursor if cursor else self.conn.cursor()
  909. if self.exist(cursor=cur):
  910. used = db_table_in_vector(self.name)
  911. if used is not None and len(used) > 0 and not force:
  912. print(
  913. _("Deleting table <%s> which is attached" " to following map(s):")
  914. % self.name
  915. )
  916. for vect in used:
  917. warning("%s" % vect)
  918. print(
  919. _("You must use the force flag to actually" " remove it. Exiting.")
  920. )
  921. else:
  922. cur.execute(sql.DROP_TAB.format(tname=self.name))
  923. def n_rows(self):
  924. """Return the number of rows
  925. >>> import sqlite3
  926. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db'
  927. >>> tab_sqlite = Table(name=test_vector_name,
  928. ... connection=sqlite3.connect(get_path(path)))
  929. >>> tab_sqlite.n_rows()
  930. 3
  931. """
  932. cur = self.conn.cursor()
  933. cur.execute(sql.SELECT.format(cols="Count(*)", tname=self.name))
  934. number = cur.fetchone()[0]
  935. cur.close()
  936. return number
  937. def execute(self, sql_code=None, cursor=None, many=False, values=None):
  938. """Execute SQL code from a given string or build with filters and
  939. return a cursor object.
  940. :param sql_code: the SQL code to execute, if not pass it use filters
  941. variable
  942. :type sql_code: str
  943. :param cursor: the cursor to connect, if None it use the cursor
  944. of connection table object
  945. :type cursor: Cursor object
  946. :param many: True to run executemany function
  947. :type many: bool
  948. :param values: The values to substitute into sql_code string
  949. :type values: list of tuple
  950. >>> import sqlite3
  951. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite/sqlite.db'
  952. >>> tab_sqlite = Table(name=test_vector_name,
  953. ... connection=sqlite3.connect(get_path(path)))
  954. >>> tab_sqlite.filters.select('cat', 'name').order_by('value')
  955. Filters('SELECT cat, name FROM table_doctest_map ORDER BY value;')
  956. >>> cur = tab_sqlite.execute()
  957. >>> cur.fetchone() #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
  958. (1, 'point')
  959. """
  960. try:
  961. sqlc = sql_code if sql_code else self.filters.get_sql()
  962. cur = cursor if cursor else self.conn.cursor()
  963. if many and values:
  964. return cur.executemany(sqlc, values)
  965. return cur.execute(sqlc, values) if values else cur.execute(sqlc)
  966. except Exception as exc:
  967. raise ValueError(
  968. "The SQL statement is not correct:\n%r,\n"
  969. "values: %r,\n"
  970. "SQL error: %s" % (sqlc, values, str(exc))
  971. )
  972. def exist(self, cursor=None):
  973. """Return True if the table already exist in the DB, False otherwise
  974. :param cursor: the cursor to connect, if None it use the cursor
  975. of connection table object
  976. """
  977. cur = cursor if cursor else self.conn.cursor()
  978. return table_exist(cur, self.name)
  979. def insert(self, values, cursor=None, many=False):
  980. """Insert a new row
  981. :param values: a tuple of values to insert, it is possible to insert
  982. more rows using a list of tuple and parameter `many`
  983. :type values: tuple
  984. :param cursor: the cursor to connect, if None it use the cursor
  985. of connection table object
  986. :type cursor: Cursor object
  987. :param many: True to run executemany function
  988. :type many: bool
  989. """
  990. cur = cursor if cursor else self.conn.cursor()
  991. if many:
  992. return cur.executemany(self.columns.insert_str, values)
  993. return cur.execute(self.columns.insert_str, values)
  994. def update(self, key, values, cursor=None):
  995. """Update a table row
  996. :param key: the rowid
  997. :type key: int
  998. :param values: the values to insert without row id.
  999. For example if we have a table with four columns:
  1000. cat, c0, c1, c2 the values list should
  1001. containing only c0, c1, c2 values.
  1002. :type values: list
  1003. :param cursor: the cursor to connect, if None it use the cursor
  1004. of connection table object
  1005. :type cursor: Cursor object
  1006. """
  1007. cur = cursor if cursor else self.conn.cursor()
  1008. vals = list(values) + [
  1009. key,
  1010. ]
  1011. return cur.execute(self.columns.update_str, vals)
  1012. def create(self, cols, name=None, overwrite=False, cursor=None):
  1013. """Create a new table
  1014. :param cols:
  1015. :type cols:
  1016. :param name: the name of table to create, None for the name of Table object
  1017. :type name: str
  1018. :param overwrite: overwrite existing table
  1019. :type overwrite: bool
  1020. :param cursor: the cursor to connect, if None it use the cursor
  1021. of connection table object
  1022. :type cursor: Cursor object
  1023. """
  1024. cur = cursor if cursor else self.conn.cursor()
  1025. coldef = ",\n".join(["%s %s" % col for col in cols])
  1026. if name:
  1027. newname = name
  1028. else:
  1029. newname = self.name
  1030. try:
  1031. cur.execute(sql.CREATE_TAB.format(tname=newname, coldef=coldef))
  1032. self.conn.commit()
  1033. except OperationalError: # OperationalError
  1034. if overwrite:
  1035. self.drop(force=True)
  1036. cur.execute(sql.CREATE_TAB.format(tname=newname, coldef=coldef))
  1037. self.conn.commit()
  1038. else:
  1039. print("The table: %s already exist." % self.name)
  1040. cur.close()
  1041. self.columns.update_odict()
  1042. if __name__ == "__main__":
  1043. import doctest
  1044. from grass.pygrass import utils
  1045. utils.create_test_vector_map(test_vector_name)
  1046. doctest.testmod()
  1047. """Remove the generated vector map, if exist"""
  1048. from grass.pygrass.utils import get_mapset_vector
  1049. from grass.script.core import run_command
  1050. mset = get_mapset_vector(test_vector_name, mapset="")
  1051. if mset:
  1052. run_command("g.remove", flags="f", type="vector", name=test_vector_name)