core.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  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. >>> import grass.temporal as tgis
  8. >>> # Create the temporal database
  9. >>> tgis.create_temporal_database()
  10. >>> # Establish a database connection
  11. >>> dbif, connected = tgis.init_dbif(None)
  12. >>> dbif.connect()
  13. >>> # Execute a SQL statement
  14. >>> dbif.execute_transaction("SELECT datetime(0, 'unixepoch', 'localtime');")
  15. >>> # Mogrify an SQL statement
  16. >>> dbif.mogrify_sql_statement(["SELECT name from raster_base where name = ?", ("precipitation",)])
  17. "SELECT name from raster_base where name = 'precipitation'"
  18. >>> dbif.close()
  19. (C) 2008-2011 by the GRASS Development Team
  20. This program is free software under the GNU General Public
  21. License (>=v2). Read the file COPYING that comes with GRASS
  22. for details.
  23. @author Soeren Gebbert
  24. """
  25. import os
  26. import copy
  27. import grass.script.core as core
  28. ###############################################################################
  29. # The chosen DBMI back-end can be defined on runtime
  30. # Check the grass environment before import
  31. core.run_command("t.connect", flags="c")
  32. kv = core.parse_command("t.connect", flags="pg")
  33. if "driver" in kv:
  34. if kv["driver"] == "sqlite":
  35. import sqlite3 as dbmi
  36. elif kv["driver"] == "pg":
  37. import psycopg2 as dbmi
  38. # Needed for dictionary like cursors
  39. import psycopg2.extras
  40. else:
  41. core.fatal(_("Unable to initialize the temporal DBMI interface. Use t.connect to specify the driver and the database string"))
  42. else:
  43. # Use the default sqlite variable
  44. core.run_command("t.connect", flags="d")
  45. import sqlite3 as dbmi
  46. ###############################################################################
  47. def get_temporal_dbmi_init_string():
  48. kv = core.parse_command("t.connect", flags="pg")
  49. grassenv = core.gisenv()
  50. if dbmi.__name__ == "sqlite3":
  51. if "database" in kv:
  52. string = kv["database"]
  53. string = string.replace("$GISDBASE", grassenv["GISDBASE"])
  54. string = string.replace(
  55. "$LOCATION_NAME", grassenv["LOCATION_NAME"])
  56. return string
  57. else:
  58. core.fatal(_("Unable to initialize the temporal GIS DBMI interface. Use t.connect to specify the driver and the database string"))
  59. elif dbmi.__name__ == "psycopg2":
  60. if "database" in kv:
  61. string = kv["database"]
  62. return string
  63. else:
  64. core.fatal(_("Unable to initialize the temporal GIS DBMI interface. Use t.connect to specify the driver and the database string"))
  65. return "dbname=grass_test user=soeren password=abcdefgh"
  66. ###############################################################################
  67. def get_sql_template_path():
  68. base = os.getenv("GISBASE")
  69. base_etc = os.path.join(base, "etc")
  70. return os.path.join(base_etc, "sql")
  71. ###############################################################################
  72. def create_temporal_database():
  73. """!This function creates the grass location database structure for raster, vector and raster3d maps
  74. as well as for the space-time datasets strds, str3ds and stvds
  75. This functions must be called before any spatio-temporal processing can be started
  76. """
  77. database = get_temporal_dbmi_init_string()
  78. db_exists = False
  79. # Check if the database already exists
  80. if dbmi.__name__ == "sqlite3":
  81. # Check path of the sqlite database
  82. if os.path.exists(database):
  83. db_exists = True
  84. elif dbmi.__name__ == "psycopg2":
  85. # Connect to database
  86. connection = dbmi.connect(database)
  87. cursor = connection.cursor()
  88. # Check for raster_base table
  89. cursor.execute("SELECT EXISTS(SELECT * FROM information_schema.tables WHERE table_name=%s)", ('raster_base',))
  90. db_exists = cursor.fetchone()[0]
  91. connection.commit()
  92. cursor.close()
  93. if db_exists == True:
  94. return
  95. core.message(_("Create temporal database: %s" % (database)))
  96. # Read all SQL scripts and templates
  97. map_tables_template_sql = open(os.path.join(
  98. get_sql_template_path(), "map_tables_template.sql"), 'r').read()
  99. raster_metadata_sql = open(os.path.join(
  100. get_sql_template_path(), "raster_metadata_table.sql"), 'r').read()
  101. raster3d_metadata_sql = open(os.path.join(get_sql_template_path(
  102. ), "raster3d_metadata_table.sql"), 'r').read()
  103. vector_metadata_sql = open(os.path.join(
  104. get_sql_template_path(), "vector_metadata_table.sql"), 'r').read()
  105. stds_tables_template_sql = open(os.path.join(
  106. get_sql_template_path(), "stds_tables_template.sql"), 'r').read()
  107. strds_metadata_sql = open(os.path.join(
  108. get_sql_template_path(), "strds_metadata_table.sql"), 'r').read()
  109. str3ds_metadata_sql = open(os.path.join(
  110. get_sql_template_path(), "str3ds_metadata_table.sql"), 'r').read()
  111. stvds_metadata_sql = open(os.path.join(
  112. get_sql_template_path(), "stvds_metadata_table.sql"), 'r').read()
  113. # Create the raster, raster3d and vector tables
  114. raster_tables_sql = map_tables_template_sql.replace("GRASS_MAP", "raster")
  115. vector_tables_sql = map_tables_template_sql.replace("GRASS_MAP", "vector")
  116. raster3d_tables_sql = map_tables_template_sql.replace(
  117. "GRASS_MAP", "raster3d")
  118. # Create the space-time raster, raster3d and vector dataset tables
  119. strds_tables_sql = stds_tables_template_sql.replace("STDS", "strds")
  120. stvds_tables_sql = stds_tables_template_sql.replace("STDS", "stvds")
  121. str3ds_tables_sql = stds_tables_template_sql.replace("STDS", "str3ds")
  122. # Connect to database
  123. connection = dbmi.connect(database)
  124. cursor = connection.cursor()
  125. if dbmi.__name__ == "sqlite3":
  126. sqlite3_delete_trigger_sql = open(os.path.join(get_sql_template_path(
  127. ), "sqlite3_delete_trigger.sql"), 'r').read()
  128. # Execute the SQL statements for sqlite
  129. # Create the global tables for the native grass datatypes
  130. cursor.executescript(raster_tables_sql)
  131. cursor.executescript(raster_metadata_sql)
  132. cursor.executescript(vector_tables_sql)
  133. cursor.executescript(vector_metadata_sql)
  134. cursor.executescript(raster3d_tables_sql)
  135. cursor.executescript(raster3d_metadata_sql)
  136. # Create the tables for the new space-time datatypes
  137. cursor.executescript(strds_tables_sql)
  138. cursor.executescript(strds_metadata_sql)
  139. cursor.executescript(stvds_tables_sql)
  140. cursor.executescript(stvds_metadata_sql)
  141. cursor.executescript(str3ds_tables_sql)
  142. cursor.executescript(str3ds_metadata_sql)
  143. cursor.executescript(sqlite3_delete_trigger_sql)
  144. elif dbmi.__name__ == "psycopg2":
  145. # Execute the SQL statements for postgresql
  146. # Create the global tables for the native grass datatypes
  147. cursor.execute(raster_tables_sql)
  148. cursor.execute(raster_metadata_sql)
  149. cursor.execute(vector_tables_sql)
  150. cursor.execute(vector_metadata_sql)
  151. cursor.execute(raster3d_tables_sql)
  152. cursor.execute(raster3d_metadata_sql)
  153. # Create the tables for the new space-time datatypes
  154. cursor.execute(strds_tables_sql)
  155. cursor.execute(strds_metadata_sql)
  156. cursor.execute(stvds_tables_sql)
  157. cursor.execute(stvds_metadata_sql)
  158. cursor.execute(str3ds_tables_sql)
  159. cursor.execute(str3ds_metadata_sql)
  160. connection.commit()
  161. cursor.close()
  162. ###############################################################################
  163. class SQLDatabaseInterfaceConnection():
  164. """!This class represents the database interface connection
  165. The following DBMS are supported:
  166. * sqlite via the sqlite3 standard library
  167. * postgresql via psycopg2
  168. """
  169. def __init__(self):
  170. self.connected = False
  171. def connect(self):
  172. """!Connect to the DBMI to execute SQL statements
  173. Supported backends are sqlite3 and postgresql
  174. """
  175. init = get_temporal_dbmi_init_string()
  176. #print "Connect to", self.database
  177. if dbmi.__name__ == "sqlite3":
  178. self.connection = dbmi.connect(init, detect_types=dbmi.PARSE_DECLTYPES | dbmi.PARSE_COLNAMES)
  179. self.connection.row_factory = dbmi.Row
  180. self.connection.isolation_level = None
  181. self.cursor = self.connection.cursor()
  182. self.cursor.execute("PRAGMA synchronous = OFF")
  183. self.cursor.execute("PRAGMA journal_mode = MEMORY")
  184. elif dbmi.__name__ == "psycopg2":
  185. self.connection = dbmi.connect(init)
  186. #self.connection.set_isolation_level(dbmi.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
  187. self.cursor = self.connection.cursor(
  188. cursor_factory=dbmi.extras.DictCursor)
  189. self.connected = True
  190. def close(self):
  191. """!Close the DBMI connection"""
  192. #print "Close connection to", self.database
  193. self.connection.commit()
  194. self.cursor.close()
  195. self.connected = False
  196. def mogrify_sql_statement(self, content):
  197. """!Return the SQL statement and arguments as executable SQL string
  198. """
  199. sql = content[0]
  200. args = content[1]
  201. if dbmi.__name__ == "psycopg2":
  202. if len(args) == 0:
  203. return sql
  204. else:
  205. if self.connected:
  206. try:
  207. return self.cursor.mogrify(sql, args)
  208. except:
  209. print sql, args
  210. raise
  211. else:
  212. self.connect()
  213. statement = self.cursor.mogrify(sql, args)
  214. self.close()
  215. return statement
  216. elif dbmi.__name__ == "sqlite3":
  217. if len(args) == 0:
  218. return sql
  219. else:
  220. # Unfortunately as sqlite does not support
  221. # the transformation of sql strings and qmarked or
  222. # named arguments we must make our hands dirty
  223. # and do it by ourself. :(
  224. # Doors are open for SQL injection because of the
  225. # limited python sqlite3 implementation!!!
  226. pos = 0
  227. count = 0
  228. maxcount = 100
  229. statement = sql
  230. while count < maxcount:
  231. pos = statement.find("?", pos + 1)
  232. if pos == -1:
  233. break
  234. if args[count] is None:
  235. statement = "%sNULL%s" % (statement[0:
  236. pos], statement[pos + 1:])
  237. elif isinstance(args[count], (int, long)):
  238. statement = "%s%d%s" % (statement[0:pos], args[count],
  239. statement[pos + 1:])
  240. elif isinstance(args[count], float):
  241. statement = "%s%f%s" % (statement[0:pos], args[count],
  242. statement[pos + 1:])
  243. else:
  244. # Default is a string, this works for datetime objects too
  245. statement = "%s\'%s\'%s" % (statement[0:pos], str(args[count]), statement[pos + 1:])
  246. count += 1
  247. return statement
  248. def execute_transaction(self, statement):
  249. """!Execute a transactional SQL statement
  250. The BEGIN and END TRANSACTION statements will be added automatically
  251. to the sql statement
  252. @param statement The executable SQL statement or SQL script
  253. """
  254. connect = False
  255. if self.connected == False:
  256. self.connect()
  257. connect = True
  258. sql_script = ""
  259. sql_script += "BEGIN TRANSACTION;\n"
  260. sql_script += statement
  261. sql_script += "END TRANSACTION;"
  262. try:
  263. if dbmi.__name__ == "sqlite3":
  264. self.cursor.executescript(statement)
  265. else:
  266. self.cursor.execute(statement)
  267. self.connection.commit()
  268. except:
  269. if connect == True:
  270. self.close()
  271. core.error(_("Unable to execute transaction:\n %s") % (statement))
  272. raise
  273. if connect:
  274. self.close()
  275. ###############################################################################
  276. def init_dbif(dbif):
  277. """!This method checks if the database interface connection exists, if not a new one
  278. will be created, connected and True will be returned
  279. Usage code sample:
  280. dbif, connect = tgis.init_dbif(dbif)
  281. if connect:
  282. dbif.close()
  283. """
  284. if dbif is None:
  285. dbif = SQLDatabaseInterfaceConnection()
  286. dbif.connect()
  287. return dbif, True
  288. return dbif, False
  289. ###############################################################################
  290. if __name__ == "__main__":
  291. import doctest
  292. doctest.testmod()