core.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850
  1. """!@package grass.temporal
  2. @brief GRASS Python scripting module (temporal GIS functions)
  3. Temporal GIS core functions to be used in library modules and scripts.
  4. This module provides the functionality to create the temporal
  5. SQL database and to establish a connection to the database.
  6. Usage:
  7. @code
  8. >>> import grass.temporal as tgis
  9. >>> # Create the temporal database
  10. >>> tgis.init()
  11. >>> # Establish a database connection
  12. >>> dbif, connected = tgis.init_dbif(None)
  13. >>> dbif.connect()
  14. >>> # Execute a SQL statement
  15. >>> dbif.execute_transaction("SELECT datetime(0, 'unixepoch', 'localtime');")
  16. >>> # Mogrify an SQL statement
  17. >>> dbif.mogrify_sql_statement(["SELECT name from raster_base where name = ?",
  18. ... ("precipitation",)])
  19. "SELECT name from raster_base where name = 'precipitation'"
  20. >>> dbif.close()
  21. @endcode
  22. (C) 2011-2013 by the GRASS Development Team
  23. This program is free software under the GNU General Public
  24. License (>=v2). Read the file COPYING that comes with GRASS
  25. for details.
  26. @author Soeren Gebbert
  27. """
  28. import sys, traceback
  29. import os
  30. import locale
  31. # i18N
  32. import gettext
  33. gettext.install('grasslibs', os.path.join(os.getenv("GISBASE"), 'locale'))
  34. import grass.script.core as core
  35. from datetime import datetime
  36. from c_libraries_interface import *
  37. # Import all supported database backends
  38. # Ignore import errors since they are checked later
  39. try:
  40. import sqlite3
  41. except ImportError:
  42. pass
  43. # Postgresql is optional, existence is checked when needed
  44. try:
  45. import psycopg2
  46. import psycopg2.extras
  47. except:
  48. pass
  49. import atexit
  50. ###############################################################################
  51. # Profiling function provided by the temporal framework
  52. def profile_function(func):
  53. do_profiling = os.getenv("GRASS_TGIS_PROFILE")
  54. if do_profiling is not None:
  55. import cProfile, pstats, StringIO
  56. pr = cProfile.Profile()
  57. pr.enable()
  58. func()
  59. pr.disable()
  60. s = StringIO.StringIO()
  61. sortby = 'cumulative'
  62. ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
  63. ps.print_stats()
  64. print s.getvalue()
  65. else:
  66. func()
  67. # Global variable that defines the backend
  68. # of the temporal GIS
  69. # It can either be "sqlite" or "pg"
  70. tgis_backend = None
  71. # The version of the temporal framework
  72. # this value must be an integer larger than 0
  73. # Increase this value in case of backward incompatible changes in the TGIS API
  74. tgis_version=2
  75. # The version of the temporal database since framework and database version can differ
  76. # this value must be an integer larger than 0
  77. # Increase this value in case of backward incompatible changes
  78. # temporal database SQL layout
  79. tgis_db_version=2
  80. # We need to access the current mapset quite often in the framework, so we make
  81. # global variable that will be initiated when init() is called
  82. current_mapset=None
  83. ###############################################################################
  84. def get_current_mapset():
  85. """!Return the current mapset
  86. This is the fastest way to receive the current mapset.
  87. The current mapset is set by init() and stored in a global variable.
  88. This function provides access to this global variable.
  89. """
  90. global current_mapset
  91. return current_mapset
  92. def _set_current_mapset(mapset=None):
  93. """!This functions set the global current mapset variable to
  94. the current mapset by calling g.gisenv.
  95. @param mapset The current mapset, g.gisenv will be called
  96. if this variable is set to None
  97. """
  98. global current_mapset
  99. if mapset == None:
  100. mapset = core.gisenv()["MAPSET"]
  101. current_mapset = mapset
  102. ###############################################################################
  103. # If this global variable is set True, then maps can only be registered in space time datasets
  104. # with the same mapset. In addition, only maps in the current mapset can be inserted, updated or deleted from
  105. # the temporal database.
  106. # Overwrite this global variable by: g.gisenv set="TGIS_DISABLE_MAPSET_CHECK=True"
  107. # ATTENTION: Be aware to face corrupted temporal database in case this global variable is set to False.
  108. # This feature is highly experimental and violates the grass permission guidance.
  109. enable_mapset_check = True
  110. # If this global variable is set True, the timestamps of maps will be written as textfiles
  111. # for each map that will be inserted or updated in the temporal database using the C-library
  112. # timestamp interface.
  113. # Overwrite this global variable by: g.gisenv set="TGIS_DISABLE_TIMESTAMP_WRITE=True"
  114. # ATTENTION: Be aware to face corrupted temporal database in case this global variable is set to False.
  115. # This feature is highly experimental and violates the grass permission guidance.
  116. enable_timestamp_write = True
  117. def get_enable_mapset_check():
  118. """!Return True if the mapsets should be checked while insert, updatem delete requests
  119. and space time dataset registration.
  120. If this global variable is set True, then maps can only be registered in space time datasets
  121. with the same mapset. In addition, only maps in the current mapset can be inserted, updated or deleted from
  122. the temporal database.
  123. Overwrite this global variable by: g.gisenv set="TGIS_DISABLE_MAPSET_CHECK=True"
  124. ATTENTION: Be aware to face corrupted temporal database in case this global variable is set to False.
  125. This feature is highly experimental and violates the grass permission guidance.
  126. """
  127. global enable_mapset_check
  128. return enable_mapset_check
  129. def get_enable_timestamp_write():
  130. """!Return True if the map timestamps should be written to the spatial database metadata as well.
  131. If this global variable is set True, the timestamps of maps will be written as textfiles
  132. for each map that will be inserted or updated in the temporal database using the C-library
  133. timestamp interface.
  134. Overwrite this global variable by: g.gisenv set="TGIS_DISABLE_TIMESTAMP_WRITE=True"
  135. ATTENTION: Be aware that C-libraries can not access timestamp informations if they are not
  136. written as spatial database metadata, hence modules that make use of timestamps
  137. using the C-library interface will not work with maps that were created without
  138. writing the timestamps.
  139. """
  140. global enable_timestamp_write
  141. return enable_timestamp_write
  142. ###############################################################################
  143. # The global variable that stores the PyGRASS Messenger object that
  144. # provides a fast and exit safe interface to the C-library message functions
  145. message_interface=None
  146. def _init_tgis_message_interface(raise_on_error=False):
  147. """!Initiate the global mesage interface
  148. @param raise_on_error If True raise a FatalError exception in case of a fatal error,
  149. call sys.exit(1) otherwise
  150. """
  151. global message_interface
  152. from grass.pygrass import messages
  153. message_interface = messages.Messenger(raise_on_error)
  154. def get_tgis_message_interface():
  155. """!Return the temporal GIS message interface which is of type
  156. grass.pyhrass.message.Messenger()
  157. Use this message interface to print messages to stdout using the
  158. GRASS C-library messaging system.
  159. """
  160. global message_interface
  161. return message_interface
  162. ###############################################################################
  163. # The global variable that stores the C-library interface object that
  164. # provides a fast and exit safe interface to the C-library libgis,
  165. # libraster, libraster3d and libvector functions
  166. c_library_interface=None
  167. def _init_tgis_c_library_interface():
  168. """!Set the global C-library interface variable that
  169. provides a fast and exit safe interface to the C-library libgis,
  170. libraster, libraster3d and libvector functions
  171. """
  172. global c_library_interface
  173. c_library_interface = CLibrariesInterface()
  174. def get_tgis_c_library_interface():
  175. """!Return the C-library interface that
  176. provides a fast and exit safe interface to the C-library libgis,
  177. libraster, libraster3d and libvector functions
  178. """
  179. global c_library_interface
  180. return c_library_interface
  181. ###############################################################################
  182. def get_tgis_version():
  183. """!Get the verion number of the temporal framework
  184. @return The version number of the temporal framework as string
  185. """
  186. global tgis_version
  187. return tgis_version
  188. ###############################################################################
  189. def get_tgis_db_version():
  190. """!Get the verion number of the temporal framework
  191. @return The version number of the temporal framework as string
  192. """
  193. global tgis_db_version
  194. return tgis_db_version
  195. ###############################################################################
  196. def get_tgis_metadata(dbif=None):
  197. """!Return the tgis metadata table as a list of rows (dicts)
  198. or None if not present
  199. @param dbif The database interface to be used
  200. @return The selected rows with key/value comumns or None
  201. """
  202. dbif, connected = init_dbif(dbif)
  203. # Select metadata if the table is present
  204. try:
  205. statement = "SELECT * FROM tgis_metadata;\n"
  206. dbif.cursor.execute(statement)
  207. rows = dbif.cursor.fetchall()
  208. except:
  209. rows = None
  210. if connected:
  211. dbif.close()
  212. return rows
  213. ###############################################################################
  214. def get_temporal_dbmi_init_string(kv=None, grassenv=None):
  215. """!Return the database initialization string
  216. @param kv dictionary generated by grass.script.parse_command("t.connect", flags="pg")
  217. @param grassenv Grass environemntal variables created by grass.script.gisenv()
  218. """
  219. global temporal_dbmi_init_string
  220. if kv == None:
  221. kv = core.parse_command("t.connect", flags="pg")
  222. if grassenv == None:
  223. grassenv = core.gisenv()
  224. global tgis_backend
  225. msgr = get_tgis_message_interface()
  226. if tgis_backend == "sqlite":
  227. # We substitute GRASS variables if they are located in the database string
  228. # This behavior is in conjunction with db.connect
  229. if "database" in kv:
  230. string = kv["database"]
  231. string = string.replace("$GISDBASE", grassenv["GISDBASE"])
  232. string = string.replace(
  233. "$LOCATION_NAME", grassenv["LOCATION_NAME"])
  234. string = string.replace("$MAPSET", grassenv["MAPSET"])
  235. temporal_dbmi_init_string = string
  236. return string
  237. else:
  238. msgr.fatal(_("Unable to initialize the temporal GIS DBMI "
  239. "interface. Use t.connect to specify the driver "
  240. "and the database string"))
  241. elif tgis_backend == "pg":
  242. if "database" in kv:
  243. string = kv["database"]
  244. temporal_dbmi_init_string = string
  245. return string
  246. else:
  247. msgr.fatal(_("Unable to initialize the temporal GIS DBMI "
  248. "interface. Use t.connect to specify the driver "
  249. "and the database string"))
  250. ###############################################################################
  251. def get_sql_template_path():
  252. base = os.getenv("GISBASE")
  253. base_etc = os.path.join(base, "etc")
  254. return os.path.join(base_etc, "sql")
  255. ###############################################################################
  256. def stop_subprocesses():
  257. """!Stop the messenger and C-interface subprocesses
  258. """
  259. global message_interface
  260. global c_library_interface
  261. if message_interface:
  262. message_interface.stop()
  263. if c_library_interface:
  264. c_library_interface.stop()
  265. atexit.register(stop_subprocesses)
  266. ###############################################################################
  267. def init():
  268. """!This function set the correct database backend from GRASS environmental variables
  269. and creates the grass location database structure for raster,
  270. vector and raster3d maps as well as for the space-time datasets strds,
  271. str3ds and stvds in case it does not exists.
  272. Several global variables are initiated and the messenger and C-library interface
  273. subprocesses are spawned.
  274. The following g.gisenv variables are checked:
  275. - TGIS_RAISE_ON_ERROR
  276. - TGIS_DISABLE_MAPSET_CHECK
  277. - TGIS_DISABLE_TIMESTAMP_WRITE
  278. The following environmental variables are checked:
  279. - GRASS_TGIS_PROFILE
  280. ATTENTION: This functions must be called before any spatio-temporal processing
  281. can be started
  282. """
  283. # We need to set the correct database backend from the environment variables
  284. global tgis_backend
  285. global enable_mapset_check
  286. global enable_timestamp_write
  287. core.run_command("t.connect", flags="c")
  288. kv = core.parse_command("t.connect", flags="pg")
  289. grassenv = core.gisenv()
  290. raise_on_error = False
  291. # Check the g.gisenv variable TGIS_RAISE_ON_ERROR
  292. if grassenv.has_key("TGIS_RAISE_ON_ERROR"):
  293. if grassenv["TGIS_RAISE_ON_ERROR"] == "True" or grassenv["TGIS_RAISE_ON_ERROR"] == "1":
  294. raise_on_error = True
  295. # Set the global variable current_mapset for fast mapset access
  296. _set_current_mapset(grassenv["MAPSET"])
  297. # Start the GRASS message interface server
  298. _init_tgis_message_interface(raise_on_error)
  299. # Start the C-library interface server
  300. _init_tgis_c_library_interface()
  301. msgr = get_tgis_message_interface()
  302. msgr.debug(1, "Inititate the temporal database")
  303. if raise_on_error is True:
  304. msgr.warning("TGIS_RAISE_ON_ERROR is True")
  305. # Set the mapset check and the timestamp write
  306. if grassenv.has_key("TGIS_DISABLE_MAPSET_CHECK"):
  307. if grassenv["TGIS_DISABLE_MAPSET_CHECK"] == "True" or grassenv["TGIS_DISABLE_MAPSET_CHECK"] == "1":
  308. enable_mapset_check = False
  309. msgr.warning("TGIS_DISABLE_MAPSET_CHECK is True")
  310. if grassenv.has_key("TGIS_DISABLE_TIMESTAMP_WRITE"):
  311. if grassenv["TGIS_DISABLE_TIMESTAMP_WRITE"] == "True" or grassenv["TGIS_DISABLE_TIMESTAMP_WRITE"] == "1":
  312. enable_timestamp_write = False
  313. msgr.warning("TGIS_DISABLE_TIMESTAMP_WRITE is True")
  314. if "driver" in kv:
  315. if kv["driver"] == "sqlite":
  316. tgis_backend = kv["driver"]
  317. try:
  318. import sqlite3
  319. except ImportError:
  320. msgr.error("Unable to locate the sqlite SQL Python interface module sqlite3.")
  321. raise
  322. dbmi = sqlite3
  323. elif kv["driver"] == "pg":
  324. tgis_backend = kv["driver"]
  325. try:
  326. import psycopg2
  327. except ImportError:
  328. msgr.error("Unable to locate the Postgresql SQL Python interface module psycopg2.")
  329. raise
  330. dbmi = psycopg2
  331. else:
  332. msgr.fatal(_("Unable to initialize the temporal DBMI interface. Use "
  333. "t.connect to specify the driver and the database string"))
  334. dbmi = sqlite3
  335. else:
  336. # Set the default sqlite3 connection in case nothing was defined
  337. core.run_command("t.connect", flags="d")
  338. db_exists = False
  339. database = get_temporal_dbmi_init_string(kv=kv, grassenv=grassenv)
  340. dbif = SQLDatabaseInterfaceConnection()
  341. # Check if the database already exists
  342. if tgis_backend == "sqlite":
  343. # Check path of the sqlite database
  344. if os.path.exists(database):
  345. dbif.connect()
  346. # Check for raster_base table
  347. dbif.cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='raster_base';")
  348. name = dbif.cursor.fetchone()
  349. if name and name[0] == "raster_base":
  350. db_exists = True
  351. dbif.close()
  352. elif tgis_backend == "pg":
  353. # Connect to database
  354. dbif.connect()
  355. # Check for raster_base table
  356. dbif.cursor.execute("SELECT EXISTS(SELECT * FROM information_schema.tables "
  357. "WHERE table_name=%s)", ('raster_base',))
  358. if dbif.cursor.fetchone()[0]:
  359. db_exists = True
  360. if db_exists == True:
  361. # Check the version of the temporal database
  362. # This version works only with database of version 2
  363. dbif.close()
  364. dbif.connect()
  365. metadata = get_tgis_metadata(dbif)
  366. dbif.close()
  367. if metadata is None:
  368. msgr.fatal(_("Unable to receiving temporal database metadata. Your temporal database is not supported."))
  369. for entry in metadata:
  370. if "tgis_version" in entry and entry[1] != str(get_tgis_version()):
  371. msgr.fatal(_("Unsupported temporal database. Version mismatch.\n"
  372. "Supported temporal API version is: %(api)i")%({"api":get_tgis_version()}))
  373. if "tgis_db_version" in entry and entry[1] != str(get_tgis_db_version()):
  374. msgr.fatal(_("Unsupported temporal database. Version mismatch.\n"
  375. "Supported temporal database version is: %(tdb)i")%( {"tdb":get_tgis_db_version()}))
  376. return
  377. create_temporal_database(dbif, database)
  378. ###############################################################################
  379. def create_temporal_database(dbif, database):
  380. """!This function will create the temporal database
  381. It will create all tables and triggers that are needed to run
  382. the temporal GIS
  383. @param dbif The database interface to be used
  384. """
  385. global tgis_backend
  386. global tgis_version
  387. global tgis_db_version
  388. template_path = get_sql_template_path()
  389. msgr = get_tgis_message_interface()
  390. # Read all SQL scripts and templates
  391. map_tables_template_sql = open(os.path.join(
  392. template_path, "map_tables_template.sql"), 'r').read()
  393. raster_metadata_sql = open(os.path.join(
  394. get_sql_template_path(), "raster_metadata_table.sql"), 'r').read()
  395. raster3d_metadata_sql = open(os.path.join(template_path,
  396. "raster3d_metadata_table.sql"),
  397. 'r').read()
  398. vector_metadata_sql = open(os.path.join(template_path,
  399. "vector_metadata_table.sql"),
  400. 'r').read()
  401. raster_views_sql = open(os.path.join(template_path, "raster_views.sql"),
  402. 'r').read()
  403. raster3d_views_sql = open(os.path.join(template_path,
  404. "raster3d_views.sql"), 'r').read()
  405. vector_views_sql = open(os.path.join(template_path, "vector_views.sql"),
  406. 'r').read()
  407. stds_tables_template_sql = open(os.path.join(template_path,
  408. "stds_tables_template.sql"),
  409. 'r').read()
  410. strds_metadata_sql = open(os.path.join(template_path,
  411. "strds_metadata_table.sql"),
  412. 'r').read()
  413. str3ds_metadata_sql = open(os.path.join(template_path,
  414. "str3ds_metadata_table.sql"),
  415. 'r').read()
  416. stvds_metadata_sql = open(os.path.join(template_path,
  417. "stvds_metadata_table.sql"),
  418. 'r').read()
  419. strds_views_sql = open(os.path.join(template_path, "strds_views.sql"),
  420. 'r').read()
  421. str3ds_views_sql = open(os.path.join(template_path, "str3ds_views.sql"),
  422. 'r').read()
  423. stvds_views_sql = open(os.path.join(template_path, "stvds_views.sql"),
  424. 'r').read()
  425. # Create the raster, raster3d and vector tables SQL statements
  426. raster_tables_sql = map_tables_template_sql.replace("GRASS_MAP", "raster")
  427. vector_tables_sql = map_tables_template_sql.replace("GRASS_MAP", "vector")
  428. raster3d_tables_sql = map_tables_template_sql.replace(
  429. "GRASS_MAP", "raster3d")
  430. # Create the space-time raster, raster3d and vector dataset tables
  431. # SQL statements
  432. strds_tables_sql = stds_tables_template_sql.replace("STDS", "strds")
  433. stvds_tables_sql = stds_tables_template_sql.replace("STDS", "stvds")
  434. str3ds_tables_sql = stds_tables_template_sql.replace("STDS", "str3ds")
  435. msgr.message(_("Create temporal database: %s" % (database)))
  436. if tgis_backend == "sqlite":
  437. # We need to create the sqlite3 database path if it does not exists
  438. tgis_dir = os.path.dirname(database)
  439. if not os.path.exists(tgis_dir):
  440. os.makedirs(tgis_dir)
  441. # Set up the trigger that takes care of
  442. # the correct deletion of entries across the different tables
  443. delete_trigger_sql = open(os.path.join(template_path,
  444. "sqlite3_delete_trigger.sql"),
  445. 'r').read()
  446. indexes_sql = open(os.path.join(template_path, "sqlite3_indexes.sql"), 'r').read()
  447. else:
  448. # Set up the trigger that takes care of
  449. # the correct deletion of entries across the different tables
  450. delete_trigger_sql = open(os.path.join(template_path,
  451. "postgresql_delete_trigger.sql"),
  452. 'r').read()
  453. indexes_sql = open(os.path.join(template_path, "postgresql_indexes.sql"), 'r').read()
  454. # Connect now to the database
  455. if not dbif.connected:
  456. dbif.connect()
  457. # Execute the SQL statements for sqlite
  458. # Create the global tables for the native grass datatypes
  459. dbif.execute_transaction(raster_tables_sql)
  460. dbif.execute_transaction(raster_metadata_sql)
  461. dbif.execute_transaction(raster_views_sql)
  462. dbif.execute_transaction(vector_tables_sql)
  463. dbif.execute_transaction(vector_metadata_sql)
  464. dbif.execute_transaction(vector_views_sql)
  465. dbif.execute_transaction(raster3d_tables_sql)
  466. dbif.execute_transaction(raster3d_metadata_sql)
  467. dbif.execute_transaction(raster3d_views_sql)
  468. # Create the tables for the new space-time datatypes
  469. dbif.execute_transaction(strds_tables_sql)
  470. dbif.execute_transaction(strds_metadata_sql)
  471. dbif.execute_transaction(strds_views_sql)
  472. dbif.execute_transaction(stvds_tables_sql)
  473. dbif.execute_transaction(stvds_metadata_sql)
  474. dbif.execute_transaction(stvds_views_sql)
  475. dbif.execute_transaction(str3ds_tables_sql)
  476. dbif.execute_transaction(str3ds_metadata_sql)
  477. dbif.execute_transaction(str3ds_views_sql)
  478. # The delete trigger
  479. dbif.execute_transaction(delete_trigger_sql)
  480. # The indexes
  481. dbif.execute_transaction(indexes_sql)
  482. # Create the tgis metadata table to store the database
  483. # initial configuration
  484. # The metadata table content
  485. metadata = {}
  486. metadata["tgis_version"] = tgis_version
  487. metadata["tgis_db_version"] = tgis_db_version
  488. metadata["creation_time"] = datetime.today()
  489. _create_tgis_metadata_table(metadata, dbif)
  490. dbif.close()
  491. ###############################################################################
  492. def _create_tgis_metadata_table(content, dbif=None):
  493. """!Create the temporal gis metadata table which stores all metadata
  494. information about the temporal database.
  495. @param content The dictionary that stores the key:value metadata
  496. that should be stored in the metadata table
  497. @param dbif The database interface to be used
  498. """
  499. dbif, connected = init_dbif(dbif)
  500. statement = "CREATE TABLE tgis_metadata (key VARCHAR NOT NULL, value VARCHAR);\n";
  501. dbif.execute_transaction(statement)
  502. for key in content.keys():
  503. statement = "INSERT INTO tgis_metadata (key, value) VALUES " + \
  504. "(\'%s\' , \'%s\');\n"%(str(key), str(content[key]))
  505. dbif.execute_transaction(statement)
  506. if connected:
  507. dbif.close()
  508. ###############################################################################
  509. class SQLDatabaseInterfaceConnection():
  510. """!This class represents the database interface connection
  511. and provides access to the chisen backend modules.
  512. The following DBMS are supported:
  513. - sqlite via the sqlite3 standard library
  514. - postgresql via psycopg2
  515. """
  516. def __init__(self):
  517. self.connected = False
  518. global tgis_backend
  519. if tgis_backend == "sqlite":
  520. self.dbmi = sqlite3
  521. else:
  522. self.dbmi = psycopg2
  523. self.msgr = get_tgis_message_interface()
  524. def rollback(self):
  525. """
  526. Roll back the last transaction. This must be called
  527. in case a new query should be performed after a db error.
  528. This is only relevant for postgresql database.
  529. """
  530. if self.dbmi.__name__ == "psycopg2":
  531. if self.connected:
  532. self.connection.rollback()
  533. def connect(self):
  534. """!Connect to the DBMI to execute SQL statements
  535. Supported backends are sqlite3 and postgresql
  536. """
  537. self.database = get_temporal_dbmi_init_string()
  538. if self.dbmi.__name__ == "sqlite3":
  539. self.connection = self.dbmi.connect(self.database,
  540. detect_types = self.dbmi.PARSE_DECLTYPES | self.dbmi.PARSE_COLNAMES)
  541. self.connection.row_factory = self.dbmi.Row
  542. self.connection.isolation_level = None
  543. self.cursor = self.connection.cursor()
  544. self.cursor.execute("PRAGMA synchronous = OFF")
  545. self.cursor.execute("PRAGMA journal_mode = MEMORY")
  546. elif self.dbmi.__name__ == "psycopg2":
  547. self.connection = self.dbmi.connect(self.database)
  548. #self.connection.set_isolation_level(dbmi.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
  549. self.cursor = self.connection.cursor(
  550. cursor_factory = self.dbmi.extras.DictCursor)
  551. self.connected = True
  552. def close(self):
  553. """!Close the DBMI connection"""
  554. #print "Close connection to", self.database
  555. self.connection.commit()
  556. self.cursor.close()
  557. self.connected = False
  558. def mogrify_sql_statement(self, content):
  559. """!Return the SQL statement and arguments as executable SQL string
  560. @param content The content as tuple with two entries, the first
  561. entry is the SQL statement with DBMI specific
  562. place holder (?), the second entry is the argument
  563. list that should substitue the place holder.
  564. Usage:
  565. @code
  566. >>> init()
  567. >>> dbif = SQLDatabaseInterfaceConnection()
  568. >>> dbif.mogrify_sql_statement(["SELECT ctime FROM raster_base WHERE id = ?",
  569. ... ["soil@PERMANENT",]])
  570. "SELECT ctime FROM raster_base WHERE id = 'soil@PERMANENT'"
  571. @endcode
  572. """
  573. sql = content[0]
  574. args = content[1]
  575. if self.dbmi.__name__ == "psycopg2":
  576. if len(args) == 0:
  577. return sql
  578. else:
  579. if self.connected:
  580. try:
  581. return self.cursor.mogrify(sql, args)
  582. except:
  583. print sql, args
  584. raise
  585. else:
  586. self.connect()
  587. statement = self.cursor.mogrify(sql, args)
  588. self.close()
  589. return statement
  590. elif self.dbmi.__name__ == "sqlite3":
  591. if len(args) == 0:
  592. return sql
  593. else:
  594. # Unfortunately as sqlite does not support
  595. # the transformation of sql strings and qmarked or
  596. # named arguments we must make our hands dirty
  597. # and do it by ourself. :(
  598. # Doors are open for SQL injection because of the
  599. # limited python sqlite3 implementation!!!
  600. pos = 0
  601. count = 0
  602. maxcount = 100
  603. statement = sql
  604. while count < maxcount:
  605. pos = statement.find("?", pos + 1)
  606. if pos == -1:
  607. break
  608. if args[count] is None:
  609. statement = "%sNULL%s" % (statement[0:pos],
  610. statement[pos + 1:])
  611. elif isinstance(args[count], (int, long)):
  612. statement = "%s%d%s" % (statement[0:pos], args[count],
  613. statement[pos + 1:])
  614. elif isinstance(args[count], float):
  615. statement = "%s%f%s" % (statement[0:pos], args[count],
  616. statement[pos + 1:])
  617. else:
  618. # Default is a string, this works for datetime
  619. # objects too
  620. statement = "%s\'%s\'%s" % (statement[0:pos],
  621. str(args[count]),
  622. statement[pos + 1:])
  623. count += 1
  624. return statement
  625. def check_table(self, table_name):
  626. """!Check if a table exists in the temporal database
  627. @param table_name The name of the table to be checked for existance
  628. @return True if the table exists, False otherwise
  629. """
  630. table_exists = False
  631. connected = False
  632. if not self.connected:
  633. self.connect()
  634. connected = True
  635. # Check if the database already exists
  636. if self.dbmi.__name__ == "sqlite3":
  637. self.cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='%s';"%table_name)
  638. name = self.cursor.fetchone()
  639. if name and name[0] == table_name:
  640. table_exists = True
  641. else:
  642. # Check for raster_base table
  643. self.cursor.execute("SELECT EXISTS(SELECT * FROM information_schema.tables "
  644. "WHERE table_name=%s)", ('%s'%table_name,))
  645. if self.cursor.fetchone()[0]:
  646. table_exists = True
  647. if connected:
  648. self.close()
  649. return table_exists
  650. def execute_transaction(self, statement):
  651. """!Execute a transactional SQL statement
  652. The BEGIN and END TRANSACTION statements will be added automatically
  653. to the sql statement
  654. @param statement The executable SQL statement or SQL script
  655. """
  656. connected = False
  657. if not self.connected:
  658. self.connect()
  659. connected = True
  660. sql_script = ""
  661. sql_script += "BEGIN TRANSACTION;\n"
  662. sql_script += statement
  663. sql_script += "END TRANSACTION;"
  664. try:
  665. if self.dbmi.__name__ == "sqlite3":
  666. self.cursor.executescript(statement)
  667. else:
  668. self.cursor.execute(statement)
  669. self.connection.commit()
  670. except:
  671. if connected:
  672. self.close()
  673. self.msgr.error(_("Unable to execute transaction:\n %(sql)s" % {"sql":statement}))
  674. raise
  675. if connected:
  676. self.close()
  677. ###############################################################################
  678. def init_dbif(dbif):
  679. """!This method checks if the database interface connection exists,
  680. if not a new one will be created, connected and True will be returned
  681. Usage code sample:
  682. @code
  683. dbif, connect = tgis.init_dbif(None)
  684. sql = dbif.mogrify_sql_statement(["SELECT * FROM raster_base WHERE ? = ?"],
  685. ["id", "soil@PERMANENT"])
  686. dbif.execute_transaction(sql)
  687. if connect:
  688. dbif.close()
  689. @endcode
  690. """
  691. if dbif is None:
  692. dbif = SQLDatabaseInterfaceConnection()
  693. dbif.connect()
  694. return dbif, True
  695. return dbif, False
  696. ###############################################################################
  697. if __name__ == "__main__":
  698. import doctest
  699. doctest.testmod()