core.py 31 KB

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