core.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  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.create_temporal_database()
  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) 2008-2011 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 os
  29. import copy
  30. import grass.script.core as core
  31. # This variable specifies if the ctypes interface to the grass
  32. # libraries should be used to read map specific data. If set to False
  33. # the grass scripting library will be used to get map informations.
  34. # The advantage of the ctypes inteface is speed, the disadvantage is that
  35. # the GRASS C functions may call G_fatal_error() which exits the process.
  36. # That is not catchable in Python.
  37. use_ctypes_map_access = True
  38. ###############################################################################
  39. # The chosen DBMI back-end can be defined on runtime
  40. # Check the grass environment before import
  41. core.run_command("t.connect", flags="c")
  42. kv = core.parse_command("t.connect", flags="pg")
  43. if "driver" in kv:
  44. if kv["driver"] == "sqlite":
  45. import sqlite3 as dbmi
  46. elif kv["driver"] == "pg":
  47. import psycopg2 as dbmi
  48. # Needed for dictionary like cursors
  49. import psycopg2.extras
  50. else:
  51. core.fatal(_("Unable to initialize the temporal DBMI interface. Use "
  52. "t.connect to specify the driver and the database string"))
  53. else:
  54. # Use the default sqlite variable
  55. core.run_command("t.connect", flags="d")
  56. import sqlite3 as dbmi
  57. ###############################################################################
  58. def get_temporal_dbmi_init_string():
  59. kv = core.parse_command("t.connect", flags="pg")
  60. grassenv = core.gisenv()
  61. if dbmi.__name__ == "sqlite3":
  62. if "database" in kv:
  63. string = kv["database"]
  64. string = string.replace("$GISDBASE", grassenv["GISDBASE"])
  65. string = string.replace(
  66. "$LOCATION_NAME", grassenv["LOCATION_NAME"])
  67. return string
  68. else:
  69. core.fatal(_("Unable to initialize the temporal GIS DBMI "
  70. "interface. Use t.connect to specify the driver "
  71. "and the database string"))
  72. elif dbmi.__name__ == "psycopg2":
  73. if "database" in kv:
  74. string = kv["database"]
  75. return string
  76. else:
  77. core.fatal(_("Unable to initialize the temporal GIS DBMI "
  78. "interface. Use t.connect to specify the driver "
  79. "and the database string"))
  80. return "dbname=grass_test user=soeren password=abcdefgh"
  81. ###############################################################################
  82. def set_use_ctypes_map_access(use_ctype = True):
  83. """!Define the map access method for the temporal GIS library
  84. Using ctypes to read map metadata is much faster
  85. then using the grass.script interface that calls grass modules.
  86. The disadvantage is that GRASS C-library function will call
  87. G_fatal_error() that will exit the calling process.
  88. GUI developer should set this flag to False.
  89. @param use_ctype: True use ctypes interface, False use grass.script interface
  90. """
  91. global use_ctypes_map_access
  92. use_ctypes_map_access = use_ctypes
  93. ###############################################################################
  94. def get_sql_template_path():
  95. base = os.getenv("GISBASE")
  96. base_etc = os.path.join(base, "etc")
  97. return os.path.join(base_etc, "sql")
  98. ###############################################################################
  99. def create_temporal_database():
  100. """!This function creates the grass location database structure for raster,
  101. vector and raster3d maps as well as for the space-time datasets strds,
  102. str3ds and stvds
  103. This functions must be called before any spatio-temporal processing
  104. can be started
  105. """
  106. database = get_temporal_dbmi_init_string()
  107. db_exists = False
  108. # Check if the database already exists
  109. if dbmi.__name__ == "sqlite3":
  110. # Check path of the sqlite database
  111. if os.path.exists(database):
  112. db_exists = True
  113. elif dbmi.__name__ == "psycopg2":
  114. # Connect to database
  115. connection = dbmi.connect(database)
  116. cursor = connection.cursor()
  117. # Check for raster_base table
  118. cursor.execute("SELECT EXISTS(SELECT * FROM information_schema.tables "
  119. "WHERE table_name=%s)", ('raster_base',))
  120. db_exists = cursor.fetchone()[0]
  121. connection.commit()
  122. cursor.close()
  123. if db_exists == True:
  124. return
  125. core.message(_("Create temporal database: %s" % (database)))
  126. # Read all SQL scripts and templates
  127. map_tables_template_sql = open(os.path.join(
  128. get_sql_template_path(), "map_tables_template.sql"), 'r').read()
  129. raster_metadata_sql = open(os.path.join(
  130. get_sql_template_path(), "raster_metadata_table.sql"), 'r').read()
  131. raster3d_metadata_sql = open(os.path.join(get_sql_template_path(
  132. ), "raster3d_metadata_table.sql"), 'r').read()
  133. vector_metadata_sql = open(os.path.join(
  134. get_sql_template_path(), "vector_metadata_table.sql"), 'r').read()
  135. stds_tables_template_sql = open(os.path.join(
  136. get_sql_template_path(), "stds_tables_template.sql"), 'r').read()
  137. strds_metadata_sql = open(os.path.join(
  138. get_sql_template_path(), "strds_metadata_table.sql"), 'r').read()
  139. str3ds_metadata_sql = open(os.path.join(
  140. get_sql_template_path(), "str3ds_metadata_table.sql"), 'r').read()
  141. stvds_metadata_sql = open(os.path.join(
  142. get_sql_template_path(), "stvds_metadata_table.sql"), 'r').read()
  143. # Create the raster, raster3d and vector tables
  144. raster_tables_sql = map_tables_template_sql.replace("GRASS_MAP", "raster")
  145. vector_tables_sql = map_tables_template_sql.replace("GRASS_MAP", "vector")
  146. raster3d_tables_sql = map_tables_template_sql.replace(
  147. "GRASS_MAP", "raster3d")
  148. # Create the space-time raster, raster3d and vector dataset tables
  149. strds_tables_sql = stds_tables_template_sql.replace("STDS", "strds")
  150. stvds_tables_sql = stds_tables_template_sql.replace("STDS", "stvds")
  151. str3ds_tables_sql = stds_tables_template_sql.replace("STDS", "str3ds")
  152. # Connect to database
  153. connection = dbmi.connect(database)
  154. cursor = connection.cursor()
  155. if dbmi.__name__ == "sqlite3":
  156. sqlite3_delete_trigger_sql = open(os.path.join(get_sql_template_path(
  157. ), "sqlite3_delete_trigger.sql"), 'r').read()
  158. # Execute the SQL statements for sqlite
  159. # Create the global tables for the native grass datatypes
  160. cursor.executescript(raster_tables_sql)
  161. cursor.executescript(raster_metadata_sql)
  162. cursor.executescript(vector_tables_sql)
  163. cursor.executescript(vector_metadata_sql)
  164. cursor.executescript(raster3d_tables_sql)
  165. cursor.executescript(raster3d_metadata_sql)
  166. # Create the tables for the new space-time datatypes
  167. cursor.executescript(strds_tables_sql)
  168. cursor.executescript(strds_metadata_sql)
  169. cursor.executescript(stvds_tables_sql)
  170. cursor.executescript(stvds_metadata_sql)
  171. cursor.executescript(str3ds_tables_sql)
  172. cursor.executescript(str3ds_metadata_sql)
  173. cursor.executescript(sqlite3_delete_trigger_sql)
  174. elif dbmi.__name__ == "psycopg2":
  175. # Execute the SQL statements for postgresql
  176. # Create the global tables for the native grass datatypes
  177. cursor.execute(raster_tables_sql)
  178. cursor.execute(raster_metadata_sql)
  179. cursor.execute(vector_tables_sql)
  180. cursor.execute(vector_metadata_sql)
  181. cursor.execute(raster3d_tables_sql)
  182. cursor.execute(raster3d_metadata_sql)
  183. # Create the tables for the new space-time datatypes
  184. cursor.execute(strds_tables_sql)
  185. cursor.execute(strds_metadata_sql)
  186. cursor.execute(stvds_tables_sql)
  187. cursor.execute(stvds_metadata_sql)
  188. cursor.execute(str3ds_tables_sql)
  189. cursor.execute(str3ds_metadata_sql)
  190. connection.commit()
  191. cursor.close()
  192. ###############################################################################
  193. class SQLDatabaseInterfaceConnection():
  194. """!This class represents the database interface connection
  195. The following DBMS are supported:
  196. * sqlite via the sqlite3 standard library
  197. * postgresql via psycopg2
  198. """
  199. def __init__(self):
  200. self.connected = False
  201. def connect(self):
  202. """!Connect to the DBMI to execute SQL statements
  203. Supported backends are sqlite3 and postgresql
  204. """
  205. init = get_temporal_dbmi_init_string()
  206. #print "Connect to", self.database
  207. if dbmi.__name__ == "sqlite3":
  208. self.connection = dbmi.connect(init,
  209. detect_types=dbmi.PARSE_DECLTYPES | dbmi.PARSE_COLNAMES)
  210. self.connection.row_factory = dbmi.Row
  211. self.connection.isolation_level = None
  212. self.cursor = self.connection.cursor()
  213. self.cursor.execute("PRAGMA synchronous = OFF")
  214. self.cursor.execute("PRAGMA journal_mode = MEMORY")
  215. elif dbmi.__name__ == "psycopg2":
  216. self.connection = dbmi.connect(init)
  217. #self.connection.set_isolation_level(dbmi.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
  218. self.cursor = self.connection.cursor(
  219. cursor_factory=dbmi.extras.DictCursor)
  220. self.connected = True
  221. def close(self):
  222. """!Close the DBMI connection"""
  223. #print "Close connection to", self.database
  224. self.connection.commit()
  225. self.cursor.close()
  226. self.connected = False
  227. def mogrify_sql_statement(self, content):
  228. """!Return the SQL statement and arguments as executable SQL string
  229. @param content: The content as tuple with two entries, the first
  230. entry is the SQL statement with DBMI specific
  231. place holder (?), the second entry is the argument
  232. list that should substitue the place holder.
  233. Usage:
  234. @code
  235. >>> dbif = SQLDatabaseInterfaceConnection()
  236. >>> dbif.mogrify_sql_statement(["SELECT ctime FROM raster_base WHERE id = ?",
  237. ... ["soil@PERMANENT",]])
  238. "SELECT ctime FROM raster_base WHERE id = 'soil@PERMANENT'"
  239. @endcode
  240. """
  241. sql = content[0]
  242. args = content[1]
  243. if dbmi.__name__ == "psycopg2":
  244. if len(args) == 0:
  245. return sql
  246. else:
  247. if self.connected:
  248. try:
  249. return self.cursor.mogrify(sql, args)
  250. except:
  251. print sql, args
  252. raise
  253. else:
  254. self.connect()
  255. statement = self.cursor.mogrify(sql, args)
  256. self.close()
  257. return statement
  258. elif dbmi.__name__ == "sqlite3":
  259. if len(args) == 0:
  260. return sql
  261. else:
  262. # Unfortunately as sqlite does not support
  263. # the transformation of sql strings and qmarked or
  264. # named arguments we must make our hands dirty
  265. # and do it by ourself. :(
  266. # Doors are open for SQL injection because of the
  267. # limited python sqlite3 implementation!!!
  268. pos = 0
  269. count = 0
  270. maxcount = 100
  271. statement = sql
  272. while count < maxcount:
  273. pos = statement.find("?", pos + 1)
  274. if pos == -1:
  275. break
  276. if args[count] is None:
  277. statement = "%sNULL%s" % (statement[0:pos],
  278. statement[pos + 1:])
  279. elif isinstance(args[count], (int, long)):
  280. statement = "%s%d%s" % (statement[0:pos], args[count],
  281. statement[pos + 1:])
  282. elif isinstance(args[count], float):
  283. statement = "%s%f%s" % (statement[0:pos], args[count],
  284. statement[pos + 1:])
  285. else:
  286. # Default is a string, this works for datetime
  287. # objects too
  288. statement = "%s\'%s\'%s" % (statement[0:pos],
  289. str(args[count]),
  290. statement[pos + 1:])
  291. count += 1
  292. return statement
  293. def execute_transaction(self, statement):
  294. """!Execute a transactional SQL statement
  295. The BEGIN and END TRANSACTION statements will be added automatically
  296. to the sql statement
  297. @param statement The executable SQL statement or SQL script
  298. """
  299. connect = False
  300. if not self.connected:
  301. self.connect()
  302. connect = True
  303. sql_script = ""
  304. sql_script += "BEGIN TRANSACTION;\n"
  305. sql_script += statement
  306. sql_script += "END TRANSACTION;"
  307. try:
  308. if dbmi.__name__ == "sqlite3":
  309. self.cursor.executescript(statement)
  310. else:
  311. self.cursor.execute(statement)
  312. self.connection.commit()
  313. except:
  314. if connect:
  315. self.close()
  316. core.error(_("Unable to execute transaction:\n %(sql)s" % \
  317. {"sql":statement}))
  318. raise
  319. if connect:
  320. self.close()
  321. ###############################################################################
  322. def init_dbif(dbif):
  323. """!This method checks if the database interface connection exists,
  324. if not a new one will be created, connected and True will be returned
  325. Usage code sample:
  326. @code
  327. dbif, connect = tgis.init_dbif(None)
  328. sql = dbif.mogrify_sql_statement(["SELECT * FROM raster_base WHERE ? = ?"],
  329. ["id", "soil@PERMANENT"])
  330. dbif.execute_transaction(sql)
  331. if connect:
  332. dbif.close()
  333. @endcode
  334. """
  335. if dbif is None:
  336. dbif = SQLDatabaseInterfaceConnection()
  337. dbif.connect()
  338. return dbif, True
  339. return dbif, False
  340. ###############################################################################
  341. if __name__ == "__main__":
  342. import doctest
  343. doctest.testmod()