core.py 14 KB

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