table.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868
  1. # -*- coding: utf-8 -*-
  2. """
  3. Created on Wed Aug 8 15:29:21 2012
  4. @author: pietro
  5. """
  6. import ctypes
  7. try:
  8. from collections import OrderedDict
  9. except:
  10. from grass.pygrass.orderdict import OrderedDict
  11. import grass.lib.vector as libvect
  12. from grass.pygrass.gis import Mapset
  13. from grass.pygrass.errors import DBError
  14. import sql
  15. DRIVERS = ('sqlite', 'pg')
  16. def get_path(path):
  17. if "$" not in path:
  18. return path
  19. else:
  20. mapset = Mapset()
  21. path = path.replace('$GISDBASE', mapset.gisdbase)
  22. path = path.replace('$LOCATION_NAME', mapset.location)
  23. path = path.replace('$MAPSET', mapset.name)
  24. return path
  25. class Filters(object):
  26. """Help user to build a simple sql query. ::
  27. >>> filter = Filters('table')
  28. >>> filter.get_sql()
  29. 'SELECT * FROM table;'
  30. >>> filter.where("area<10000").get_sql()
  31. 'SELECT * FROM table WHERE area<10000;'
  32. >>> filter.select("cat", "area").get_sql()
  33. 'SELECT cat, area FROM table WHERE area<10000;'
  34. >>> filter.order_by("area").limit(10).get_sql()
  35. 'SELECT cat, area FROM table WHERE area<10000 ORDER BY area LIMIT 10;'
  36. ..
  37. """
  38. def __init__(self, tname):
  39. self.tname = tname
  40. self._select = None
  41. self._where = None
  42. self._orderby = None
  43. self._limit = None
  44. def __repr__(self):
  45. return "Filters(%r)" % self.get_sql()
  46. def select(self, *args):
  47. cols = ', '.join(args) if args else '*'
  48. select = sql.SELECT[:-1]
  49. self._select = select.format(cols=cols, tname=self.tname)
  50. return self
  51. def where(self, condition):
  52. self._where = 'WHERE {condition}'.format(condition=condition)
  53. return self
  54. def order_by(self, orderby):
  55. if not isinstance(orderby, str):
  56. orderby = ', '.join(orderby)
  57. self._orderby = 'ORDER BY {orderby}'.format(orderby=orderby)
  58. return self
  59. def limit(self, number):
  60. if not isinstance(number, int):
  61. raise ValueError("Must be an integer.")
  62. else:
  63. self._limit = 'LIMIT {number}'.format(number=number)
  64. return self
  65. def get_sql(self):
  66. sql_list = list()
  67. if self._select is None:
  68. self.select()
  69. sql_list.append(self._select)
  70. if self._where is not None:
  71. sql_list.append(self._where)
  72. if self._orderby is not None:
  73. sql_list.append(self._orderby)
  74. if self._limit is not None:
  75. sql_list.append(self._limit)
  76. return "%s;" % ' '.join(sql_list)
  77. def reset(self):
  78. self._select = None
  79. self._where = None
  80. self._orderby = None
  81. self._limit = None
  82. class Columns(object):
  83. """Object to work with columns table.
  84. It is possible to instantiate a Columns object given the table name and
  85. the database connection.
  86. For a sqlite table: ::
  87. >>> import sqlite3
  88. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db'
  89. >>> cols_sqlite = Columns('boundary_municp_sqlite',
  90. ... sqlite3.connect(get_path(path)))
  91. >>> cols_sqlite.tname
  92. 'boundary_municp_sqlite'
  93. For a postgreSQL table: ::
  94. >>> import psycopg2 as pg
  95. >>> cols_pg = Columns('boundary_municp_pg',
  96. ... pg.connect('host=localhost dbname=grassdb'))
  97. >>> cols_pg.tname
  98. 'boundary_municp_pg'
  99. ..
  100. """
  101. def __init__(self, tname, connection, key='cat'):
  102. self.tname = tname
  103. self.conn = connection
  104. self.key = key
  105. self.odict = None
  106. self.update_odict()
  107. def __contains__(self, item):
  108. return item in self.names()
  109. def __repr__(self):
  110. return "Columns(%r)" % self.items()
  111. def __getitem__(self, key):
  112. return self.odict[key]
  113. def __setitem__(self, name, new_type):
  114. self.cast(name, new_type)
  115. self.update_odict(self)
  116. def __iter__(self):
  117. return self.odict.__iter__()
  118. def __len__(self):
  119. return self.odict.__len__()
  120. def __eq__(self, obj):
  121. return obj.tname == self.tname and obj.odict == self.odict
  122. def is_pg(self):
  123. """Return True if is a psycopg connection. ::
  124. >>> import sqlite3
  125. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db'
  126. >>> cols_sqlite = Columns('boundary_municp_sqlite',
  127. ... sqlite3.connect(get_path(path)))
  128. >>> cols_sqlite.is_pg()
  129. False
  130. >>> import psycopg2 as pg
  131. >>> cols_pg = Columns('boundary_municp_pg',
  132. ... pg.connect('host=localhost dbname=grassdb'))
  133. >>> cols_pg.is_pg()
  134. True
  135. ..
  136. """
  137. return hasattr(self.conn, 'xid')
  138. def update_odict(self):
  139. """Read columns name and types from table and update the odict
  140. attribute.
  141. """
  142. if self.is_pg():
  143. # is a postgres connection
  144. cur = self.conn.cursor()
  145. cur.execute("SELECT oid,typname FROM pg_type")
  146. diz = dict(cur.fetchall())
  147. odict = OrderedDict()
  148. import psycopg2 as pg
  149. try:
  150. cur.execute(sql.SELECT.format(cols='*', tname=self.tname))
  151. descr = cur.description
  152. for column in descr:
  153. name, ctype = column[:2]
  154. odict[name] = diz[ctype]
  155. except pg.ProgrammingError:
  156. pass
  157. self.odict = odict
  158. else:
  159. # is a sqlite connection
  160. cur = self.conn.cursor()
  161. cur.execute(sql.PRAGMA.format(tname=self.tname))
  162. descr = cur.fetchall()
  163. odict = OrderedDict()
  164. for column in descr:
  165. name, ctype = column[1:3]
  166. odict[name] = ctype
  167. self.odict = odict
  168. values = ','.join(['?', ] * self.__len__())
  169. kv = ','.join(['%s=?' % k for k in self.odict.keys()])
  170. where = "%s=?" % self.key
  171. self.insert_str = sql.INSERT.format(tname=self.tname, values=values)
  172. self.update_str = sql.UPDATE_WHERE.format(tname=self.tname,
  173. values=kv, condition=where)
  174. def sql_descr(self, remove=None):
  175. """Return a string with description of columns.
  176. Remove it is used to remove a columns.::
  177. >>> import sqlite3
  178. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db'
  179. >>> cols_sqlite = Columns('boundary_municp_sqlite',
  180. ... sqlite3.connect(get_path(path)))
  181. >>> cols_sqlite.sql_descr() # doctest: +ELLIPSIS
  182. u'cat integer, OBJECTID integer, AREA double precision, ...'
  183. >>> import psycopg2 as pg
  184. >>> cols_pg = Columns('boundary_municp_pg',
  185. ... pg.connect('host=localhost dbname=grassdb'))
  186. >>> cols_pg.sql_descr() # doctest: +ELLIPSIS
  187. 'cat int4, objectid int4, area float8, perimeter float8, ...'
  188. """
  189. if remove:
  190. return ', '.join(['%s %s' % (key, val) for key, val in self.items()
  191. if key != remove])
  192. else:
  193. return ', '.join(['%s %s' % (key, val)
  194. for key, val in self.items()])
  195. def types(self):
  196. """Return a list with the column types. ::
  197. >>> import sqlite3
  198. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db'
  199. >>> cols_sqlite = Columns('boundary_municp_sqlite',
  200. ... sqlite3.connect(get_path(path)))
  201. >>> cols_sqlite.types() # doctest: +ELLIPSIS
  202. [u'integer', u'integer', ...]
  203. >>> import psycopg2 as pg
  204. >>> cols_pg = Columns('boundary_municp_pg',
  205. ... pg.connect('host=localhost dbname=grassdb'))
  206. >>> cols_pg.types() # doctest: +ELLIPSIS
  207. ['int4', 'int4', 'float8', 'float8', 'float8', ...]
  208. ..
  209. """
  210. return self.odict.values()
  211. def names(self, remove=None, unicod=True):
  212. """Return a list with the column names.
  213. Remove it is used to remove a columns.::
  214. >>> import sqlite3
  215. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db'
  216. >>> cols_sqlite = Columns('boundary_municp_sqlite',
  217. ... sqlite3.connect(get_path(path)))
  218. >>> cols_sqlite.names() # doctest: +ELLIPSIS
  219. [u'cat', u'OBJECTID', u'AREA', u'PERIMETER', ...]
  220. >>> import psycopg2 as pg
  221. >>> cols_pg = Columns('boundary_municp_pg',
  222. ... pg.connect('host=localhost dbname=grassdb'))
  223. >>> cols_pg.names() # doctest: +ELLIPSIS
  224. ['cat', 'objectid', 'area', 'perimeter', ...]
  225. ..
  226. """
  227. if remove:
  228. nams = self.odict.keys()
  229. nams.remove(remove)
  230. else:
  231. nams = self.odict.keys()
  232. if unicod:
  233. return nams
  234. else:
  235. return [str(name) for name in nams]
  236. def items(self):
  237. """Return a list of tuple with column name and column type. ::
  238. >>> import sqlite3
  239. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db'
  240. >>> cols_sqlite = Columns('boundary_municp_sqlite',
  241. ... sqlite3.connect(get_path(path)))
  242. >>> cols_sqlite.items() # doctest: +ELLIPSIS
  243. [(u'cat', u'integer'), ...]
  244. >>> import psycopg2 as pg
  245. >>> cols_pg = Columns('boundary_municp_pg',
  246. ... pg.connect('host=localhost dbname=grassdb'))
  247. >>> cols_pg.items() # doctest: +ELLIPSIS
  248. [('cat', 'int4'), ('objectid', 'int4'), ('area', 'float8'), ...]
  249. ..
  250. """
  251. return self.odict.items()
  252. def create(self, cols):
  253. """"""
  254. cur = self.conn.cursor()
  255. coldef = ',\n'.join(['%s %s' % col for col in cols])
  256. cur.execute(sql.CREATE_TAB.format(tname=self.tname, coldef=coldef))
  257. self.conn.commit()
  258. cur.close()
  259. self.update_odict()
  260. def add(self, col_name, col_type):
  261. """Add a new column to the table. ::
  262. >>> import sqlite3
  263. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db'
  264. >>> cols_sqlite = Columns('boundary_municp_sqlite',
  265. ... sqlite3.connect(get_path(path)))
  266. >>> cols_sqlite.add('n_pizza', 'int4')
  267. >>> 'n_pizza' in cols_sqlite
  268. True
  269. >>> import psycopg2 as pg
  270. >>> cols_pg = Columns('boundary_municp_pg',
  271. ... pg.connect('host=localhost dbname=grassdb'))
  272. >>> cols_pg.add('n_pizza', 'int4')
  273. >>> 'n_pizza' in cols_pg
  274. True
  275. ..
  276. """
  277. valid_type = ('DOUBLE PRECISION', 'INT', 'DATE')
  278. if 'VARCHAR' in col_type or col_type.upper() not in valid_type:
  279. str_err = "Type is not supported, supported types are: %s"
  280. raise TypeError(str_err % ", ".join(valid_type))
  281. cur = self.conn.cursor()
  282. cur.execute(sql.ADD_COL.format(tname=self.tname,
  283. cname=col_name,
  284. ctype=col_type))
  285. self.conn.commit()
  286. cur.close()
  287. self.update_odict()
  288. def rename(self, old_name, new_name):
  289. """Rename a column of the table. ::
  290. >>> import sqlite3
  291. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db'
  292. >>> cols_sqlite = Columns('boundary_municp_sqlite',
  293. ... sqlite3.connect(get_path(path)))
  294. >>> cols_sqlite.rename('n_pizza', 'n_pizzas') # doctest: +ELLIPSIS
  295. >>> 'n_pizza' in cols_sqlite
  296. False
  297. >>> 'n_pizzas' in cols_sqlite
  298. True
  299. >>> import psycopg2 as pg
  300. >>> cols_pg = Columns('boundary_municp_pg',
  301. ... pg.connect('host=localhost dbname=grassdb'))
  302. >>> cols_pg.rename('n_pizza', 'n_pizzas')
  303. >>> 'n_pizza' in cols_pg
  304. False
  305. >>> 'n_pizzas' in cols_pg
  306. True
  307. ..
  308. """
  309. cur = self.conn.cursor()
  310. if self.is_pg():
  311. cur.execute(sql.RENAME_COL.format(tname=self.tname,
  312. old_name=old_name,
  313. new_name=new_name))
  314. self.conn.commit()
  315. cur.close()
  316. self.update_odict()
  317. else:
  318. cur.execute(sql.ADD_COL.format(tname=self.tname,
  319. cname=new_name,
  320. ctype=str(self.odict[old_name])))
  321. cur.execute(sql.UPDATE.format(tname=self.tname,
  322. new_col=new_name,
  323. old_col=old_name))
  324. self.conn.commit()
  325. cur.close()
  326. self.update_odict()
  327. self.drop(old_name)
  328. def cast(self, col_name, new_type):
  329. """Change the column type. ::
  330. >>> import sqlite3
  331. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db'
  332. >>> cols_sqlite = Columns('boundary_municp_sqlite',
  333. ... sqlite3.connect(get_path(path)))
  334. >>> cols_sqlite.cast('n_pizzas', 'float8') # doctest: +ELLIPSIS
  335. Traceback (most recent call last):
  336. ...
  337. DBError: 'SQLite does not support to cast columns.'
  338. >>> import psycopg2 as pg
  339. >>> cols_pg = Columns('boundary_municp_pg',
  340. ... pg.connect('host=localhost dbname=grassdb'))
  341. >>> cols_pg.cast('n_pizzas', 'float8')
  342. >>> cols_pg['n_pizzas']
  343. 'float8'
  344. .. warning ::
  345. It is not possible to cast a column with sqlite
  346. ..
  347. """
  348. if self.is_pg():
  349. cur = self.conn.cursor()
  350. cur.execute(sql.CAST_COL.format(tname=self.tname, col=col_name,
  351. ctype=new_type))
  352. self.conn.commit()
  353. cur.close()
  354. self.update_odict()
  355. else:
  356. # sqlite does not support rename columns:
  357. raise DBError('SQLite does not support to cast columns.')
  358. def drop(self, col_name):
  359. """Drop a column from the table. ::
  360. >>> import sqlite3
  361. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db'
  362. >>> cols_sqlite = Columns('boundary_municp_sqlite',
  363. ... sqlite3.connect(get_path(path)))
  364. >>> cols_sqlite.drop('n_pizzas') # doctest: +ELLIPSIS
  365. >>> 'n_pizzas' in cols_sqlite
  366. False
  367. >>> import psycopg2 as pg
  368. >>> cols_pg = Columns('boundary_municp_pg',
  369. ... pg.connect('host=localhost dbname=grassdb'))
  370. >>> cols_pg.drop('n_pizzas')
  371. >>> 'n_pizzas' in cols_pg
  372. False
  373. ..
  374. """
  375. cur = self.conn.cursor()
  376. if self.is_pg():
  377. cur.execute(sql.DROP_COL.format(tname=self.tname,
  378. cname=col_name))
  379. else:
  380. desc = str(self.sql_descr(remove=col_name))
  381. names = ', '.join(self.names(remove=col_name, unicod=False))
  382. queries = sql.DROP_COL_SQLITE.format(tname=self.tname,
  383. keycol=self.key,
  384. coldef=desc,
  385. colnames=names).split('\n')
  386. for query in queries:
  387. cur.execute(query)
  388. self.conn.commit()
  389. cur.close()
  390. self.update_odict()
  391. class Link(object):
  392. """Define a Link between vector map and the attributes table.
  393. It is possible to define a Link object or given all the information
  394. (layer, name, table name, key, database, driver): ::
  395. >>> link = Link(1, 'link0', 'boundary_municp_sqlite', 'cat',
  396. ... '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db', 'sqlite')
  397. >>> link.number
  398. 1
  399. >>> link.name
  400. 'link0'
  401. >>> link.table_name
  402. 'boundary_municp_sqlite'
  403. >>> link.key
  404. 'cat'
  405. >>> link.database
  406. '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db'
  407. >>> link.driver
  408. 'sqlite'
  409. >>> link
  410. Link(1, link0, sqlite)
  411. It is possible to change parameters with: ::
  412. >>> link.driver = 'pg'
  413. >>> link.driver
  414. 'pg'
  415. >>> link.driver = 'postgres' # doctest: +ELLIPSIS
  416. Traceback (most recent call last):
  417. ...
  418. TypeError: Driver not supported, use: sqlite, pg.
  419. >>> link.driver
  420. 'pg'
  421. >>> link.number = 0 # doctest: +ELLIPSIS
  422. Traceback (most recent call last):
  423. ...
  424. TypeError: Number must be positive and greater than 0.
  425. Or given a c_fieldinfo object that is a ctypes pointer to the field_info C
  426. struct. ::
  427. >>> link = Link(c_fieldinfo = ctypes.pointer(libvect.field_info()))
  428. ..
  429. """
  430. def _get_layer(self):
  431. return self.c_fieldinfo.contents.number
  432. def _set_layer(self, number):
  433. if number <= 0:
  434. raise TypeError("Number must be positive and greater than 0.")
  435. self.c_fieldinfo.contents.number = number
  436. layer = property(fget=_get_layer, fset=_set_layer)
  437. def _get_name(self):
  438. return self.c_fieldinfo.contents.name
  439. def _set_name(self, name):
  440. self.c_fieldinfo.contents.name = name
  441. name = property(fget=_get_name, fset=_set_name)
  442. def _get_table(self):
  443. return self.c_fieldinfo.contents.table
  444. def _set_table(self, new_name):
  445. self.c_fieldinfo.contents.table = new_name
  446. table_name = property(fget=_get_table, fset=_set_table)
  447. def _get_key(self):
  448. return self.c_fieldinfo.contents.key
  449. def _set_key(self, key):
  450. self.c_fieldinfo.contents.key = key
  451. key = property(fget=_get_key, fset=_set_key)
  452. def _get_database(self):
  453. return self.c_fieldinfo.contents.database
  454. def _set_database(self, database):
  455. self.c_fieldinfo.contents.database = database
  456. database = property(fget=_get_database, fset=_set_database)
  457. def _get_driver(self):
  458. return self.c_fieldinfo.contents.driver
  459. def _set_driver(self, driver):
  460. if driver not in ('sqlite', 'pg'):
  461. str_err = "Driver not supported, use: %s." % ", ".join(DRIVERS)
  462. raise TypeError(str_err)
  463. self.c_fieldinfo.contents.driver = driver
  464. driver = property(fget=_get_driver, fset=_set_driver)
  465. def __init__(self, layer=1, name=None, table=None, key=None,
  466. database=None, driver=None, c_fieldinfo=None):
  467. if c_fieldinfo is not None:
  468. self.c_fieldinfo = c_fieldinfo
  469. else:
  470. self.c_fieldinfo = ctypes.pointer(libvect.field_info())
  471. self.layer = layer
  472. self.name = name
  473. self.table_name = table
  474. self.key = key
  475. self.database = database
  476. self.driver = driver
  477. def __repr__(self):
  478. return "Link(%d, %s, %s)" % (self.layer, self.name, self.driver)
  479. def connection(self):
  480. """Return a connection object. ::
  481. >>> link = Link(1, 'link0', 'boundary_municp_sqlite', 'cat',
  482. ... '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db',
  483. ... 'sqlite')
  484. >>> conn = link.connection()
  485. >>> cur = conn.cursor()
  486. >>> cur.execute("SELECT cat,COUNTY,PERIMETER FROM %s" %
  487. ... link.table_name) # doctest: +ELLIPSIS
  488. <sqlite3.Cursor object at ...>
  489. >>> cur.fetchone()
  490. (1, u'SURRY', 1415.331)
  491. >>> cur.close()
  492. >>> conn.close()
  493. ...
  494. """
  495. if self.driver == 'sqlite':
  496. import sqlite3
  497. return sqlite3.connect(get_path(self.database))
  498. elif self.driver == 'pg':
  499. try:
  500. import psycopg2
  501. db = ' '.join(self.database.split(','))
  502. return psycopg2.connect(db)
  503. except ImportError:
  504. er = "You need to install psycopg2 to connect with this table."
  505. raise ImportError(er)
  506. else:
  507. str_err = "Driver is not supported yet, pleas use: sqlite or pg"
  508. raise TypeError(str_err)
  509. def table(self):
  510. """Return a Table object. ::
  511. >>> link = Link(1, 'link0', 'boundary_municp_sqlite', 'cat',
  512. ... '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db',
  513. ... 'sqlite')
  514. >>> table = link.table()
  515. >>> table.filters.select('cat', 'COUNTY', 'PERIMETER')
  516. Filters('SELECT cat, COUNTY, PERIMETER FROM boundary_municp_sqlite;')
  517. >>> cur = table.execute()
  518. >>> cur.fetchone()
  519. (1, u'SURRY', 1415.331)
  520. >>> cur.close()
  521. ..
  522. """
  523. return Table(self.table_name, self.connection(), self.key)
  524. def info(self):
  525. """Print information of the link. ::
  526. >>> link = Link(1, 'link0', 'boundary_municp_sqlite', 'cat',
  527. ... '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db',
  528. ... 'sqlite')
  529. >>> link.info()
  530. layer: 1
  531. name: link0
  532. table: boundary_municp_sqlite
  533. key: cat
  534. database: $GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db
  535. driver: sqlite
  536. ..
  537. """
  538. print "layer: ", self.layer
  539. print "name: ", self.name
  540. print "table: ", self.table_name
  541. print "key: ", self.key
  542. print "database: ", self.database
  543. print "driver: ", self.driver
  544. class DBlinks(object):
  545. """Interface containing link to the table DB. ::
  546. >>> from grass.pygrass.vector import VectorTopo
  547. >>> municip = VectorTopo('boundary_municp_sqlite')
  548. >>> municip.open()
  549. >>> dblinks = DBlinks(municip.c_mapinfo)
  550. >>> dblinks
  551. DBlinks([Link(1, boundary_municp, sqlite)])
  552. >>> dblinks[1]
  553. Link(1, boundary_municp, sqlite)
  554. >>> dblinks[0] # doctest: +ELLIPSIS
  555. Traceback (most recent call last):
  556. ...
  557. TypeError: The index must be != 0.
  558. >>> dblinks['boundary_municp']
  559. Link(1, boundary_municp, sqlite)
  560. ..
  561. """
  562. def __init__(self, c_mapinfo):
  563. self.c_mapinfo = c_mapinfo
  564. def __len__(self):
  565. return self.num_dblinks()
  566. def __iter__(self):
  567. return (self.by_index(i)
  568. for i in xrange(self.num_dblinks()))
  569. def __getitem__(self, item):
  570. """
  571. """
  572. if isinstance(item, int):
  573. return self.by_index(item)
  574. else:
  575. return self.by_name(item)
  576. def __repr__(self):
  577. return "DBlinks(%r)" % [link for link in self.__iter__()]
  578. def by_index(self, indx):
  579. c_fieldinfo = libvect.Vect_get_dblink(self.c_mapinfo, indx)
  580. return Link(c_fieldinfo=c_fieldinfo)
  581. def by_layer(self, layer):
  582. c_fieldinfo = libvect.Vect_get_field(self.c_mapinfo, layer)
  583. return Link(c_fieldinfo=c_fieldinfo)
  584. def by_name(self, name):
  585. c_fieldinfo = libvect.Vect_get_field_by_name(self.c_mapinfo, name)
  586. return Link(c_fieldinfo=c_fieldinfo)
  587. def num_dblinks(self):
  588. return libvect.Vect_get_num_dblinks(self.c_mapinfo)
  589. def add(self, link):
  590. """Add a new link. ::
  591. >>> from grass.pygrass.vector import VectorTopo
  592. >>> municip = VectorTopo('boundary_municp_sqlite')
  593. >>> municip.open()
  594. >>> dblinks = DBlinks(municip.c_mapinfo)
  595. >>> dblinks
  596. DBlinks([Link(1, boundary_municp, sqlite)])
  597. >>> link = Link(2, 'pg_link', 'boundary_municp_pg', 'cat',
  598. ... 'host=localhost dbname=grassdb', 'pg')
  599. >>> dblinks.add(link)
  600. >>> dblinks # need to open vector map in write mode
  601. DBlinks([Link(1, boundary_municp, sqlite)])
  602. ..
  603. """
  604. #TODO: check if open in write mode or not.
  605. libvect.Vect_map_add_dblink(self.c_mapinfo,
  606. link.layer, link.name, link.table_name,
  607. link.key, link.database, link.driver)
  608. def remove(self, key):
  609. """Remove a link. ::
  610. >>> from grass.pygrass.vector import VectorTopo
  611. >>> municip = VectorTopo('boundary_municp_sqlite')
  612. >>> municip.open()
  613. >>> dblinks = DBlinks(municip.c_mapinfo)
  614. >>> dblinks
  615. DBlinks([Link(1, boundary_municp, sqlite)])
  616. >>> dblinks.remove('pg_link')
  617. >>> dblinks # need to open vector map in write mode
  618. DBlinks([Link(1, boundary_municp, sqlite)])
  619. ..
  620. """
  621. if isinstance(key, str):
  622. key = self.from_name_to_num(key)
  623. libvect.Vect_map_del_dblink(self.c_mapinfo, key)
  624. def from_name_to_num(self, name):
  625. """
  626. Vect_get_field_number
  627. """
  628. return libvect.Vect_get_field_number(self.c_mapinfo, name)
  629. class Table(object):
  630. """::
  631. >>> import sqlite3
  632. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db'
  633. >>> tab_sqlite = Table(name='boundary_municp_sqlite',
  634. ... connection=sqlite3.connect(get_path(path)))
  635. >>> tab_sqlite.name
  636. 'boundary_municp_sqlite'
  637. >>> import psycopg2
  638. >>> tab_pg = Table('boundary_municp_pg',
  639. ... psycopg2.connect('host=localhost dbname=grassdb',
  640. ... 'pg'))
  641. >>> tab_pg.columns # doctest: +ELLIPSIS
  642. Columns([('cat', 'int4'), ...])
  643. ..
  644. """
  645. def _get_name(self):
  646. return self._name
  647. def _set_name(self, new_name):
  648. old_name = self._name
  649. cur = self.conn.cursor()
  650. cur.execute(sql.RENAME_TAB.format(old_name=old_name,
  651. new_name=new_name))
  652. cur.commit()
  653. cur.close()
  654. name = property(fget=_get_name, fset=_set_name)
  655. def __init__(self, name, connection, key='cat'):
  656. self._name = name
  657. self.conn = connection
  658. self.key = key
  659. self.columns = Columns(self.name,
  660. self.conn,
  661. self.key)
  662. self.filters = Filters(self.name)
  663. def __repr__(self):
  664. """::
  665. >>> import sqlite3
  666. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db'
  667. >>> tab_sqlite = Table(name='boundary_municp_sqlite',
  668. ... connection=sqlite3.connect(get_path(path)))
  669. >>> tab_sqlite
  670. Table('boundary_municp_sqlite')
  671. ..
  672. """
  673. return "Table(%r)" % (self.name)
  674. def __iter__(self):
  675. cur = self.execute()
  676. return (cur.fetchone() for _ in xrange(self.__len__()))
  677. def __len__(self):
  678. """Return the nuber of rows"""
  679. return self.n_rows()
  680. def n_rows(self):
  681. cur = self.conn.cursor()
  682. cur.execute(sql.SELECT.format(cols='Count(*)', tname=self.name))
  683. number = cur.fetchone()[0]
  684. cur.close()
  685. return number
  686. def execute(self, sql_code=None):
  687. """Execute SQL code from a given string or build with filters and
  688. return a cursor object. ::
  689. >>> import sqlite3
  690. >>> path = '$GISDBASE/$LOCATION_NAME/$MAPSET/sqlite.db'
  691. >>> tab_sqlite = Table(name='boundary_municp_sqlite',
  692. ... connection=sqlite3.connect(get_path(path)))
  693. >>> tab_sqlite.filters.select('cat', 'COUNTY').order_by('AREA')
  694. Filters('SELECT cat, COUNTY FROM boundary_municp_sqlite ORDER BY AREA;')
  695. >>> cur = tab_sqlite.execute()
  696. >>> cur.fetchone()
  697. (1, u'SURRY')
  698. ..
  699. """
  700. try:
  701. sqlc = sql_code if sql_code else self.filters.get_sql()
  702. cur = self.conn.cursor()
  703. #if hasattr(self.cur, 'executescript'):
  704. # return cur.executescript(sqlc)
  705. return cur.execute(sqlc)
  706. except:
  707. raise ValueError("The SQL is not correct:\n%r" % sqlc)
  708. def insert(self, values, many=False):
  709. cur = self.conn.cursor()
  710. if many:
  711. return cur.executemany(self.columns.insert_str, values)
  712. return cur.execute(self.columns.insert_str, values)
  713. def update(self, key, values):
  714. cur = self.conn.cursor()
  715. vals = list(values)
  716. vals.append(key)
  717. return cur.execute(self.columns.update_str, vals)