sqlbuilder.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. """!
  2. @package sqlbuilder.py
  3. @brief GRASS SQL Builder
  4. Classes:
  5. - SQLFrame
  6. @todo Various updates are required...
  7. Usage:
  8. @code
  9. python sqlbuilder.py vector_map
  10. @endcode
  11. (C) 2007-2009 by the GRASS Development Team
  12. This program is free software under the GNU General Public
  13. License (>=v2). Read the file COPYING that comes with GRASS
  14. for details.
  15. @author Jachym Cepicky <jachym.cepicky gmail.com> (original author)
  16. @author Martin Landa <landa.martin gmail.com>,
  17. @author Hamish Bowman <hamish_b yahoo com>
  18. """
  19. import os
  20. import sys
  21. import time
  22. ### i18N
  23. import gettext
  24. gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode=True)
  25. import globalvar
  26. if not os.getenv("GRASS_WXBUNDLED"):
  27. globalvar.CheckForWx()
  28. import wx
  29. import grass.script as grass
  30. grass.set_fatal_exit(False)
  31. import gcmd
  32. import dbm_base
  33. class SQLFrame(wx.Frame):
  34. """!SQL Frame class"""
  35. def __init__(self, parent, title, vectmap, id = wx.ID_ANY,
  36. layer = 1, qtype = "select", evtheader = None):
  37. wx.Frame.__init__(self, parent, id, title)
  38. self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass_sql.ico'),
  39. wx.BITMAP_TYPE_ICO))
  40. self.parent = parent
  41. self.evtHeader = evtheader
  42. #
  43. # variables
  44. #
  45. self.vectmap = vectmap # fullname
  46. if not "@" in self.vectmap:
  47. self.vectmap = self.vectmap + "@" + grass.gisenv()['MAPSET']
  48. self.mapname, self.mapset = self.vectmap.split("@")
  49. # db info
  50. self.layer = layer
  51. self.dbInfo = dbm_base.VectorDBInfo(self.vectmap)
  52. self.tablename = self.dbInfo.GetTable(self.layer)
  53. self.driver, self.database = self.dbInfo.GetDbSettings(self.layer)
  54. self.qtype = qtype # type of query: SELECT, UPDATE, DELETE, ...
  55. self.colvalues = [] # array with unique values in selected column
  56. # set dialog title
  57. self.SetTitle(_("GRASS SQL Builder (%(type)s): vector map <%(map)s>") % \
  58. { 'type' : qtype.upper(), 'map' : self.vectmap })
  59. self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
  60. # statusbar
  61. self.statusbar = self.CreateStatusBar(number=1)
  62. self.statusbar.SetStatusText(_("SQL statement not verified"), 0)
  63. #
  64. # buttons
  65. #
  66. self.btn_clear = wx.Button(parent = self.panel, id = wx.ID_CLEAR)
  67. self.btn_clear.SetToolTipString(_("Set SQL statement to default"))
  68. self.btn_verify = wx.Button(parent = self.panel, id = wx.ID_ANY,
  69. label = _("Verify"))
  70. self.btn_verify.SetToolTipString(_("Verify SQL statement"))
  71. # self.btn_help = wx.Button(self.panel, -1, "Help")
  72. # self.btn_load = wx.Button(self.panel, -1, "Load")
  73. # self.btn_save = wx.Button(self.panel, -1, "Save")
  74. self.btn_apply = wx.Button(parent = self.panel, id = wx.ID_APPLY)
  75. self.btn_apply.SetToolTipString(_("Apply SQL statement and close the dialog"))
  76. self.btn_close = wx.Button(parent = self.panel, id = wx.ID_CLOSE)
  77. self.btn_close.SetToolTipString(_("Close the dialog"))
  78. self.btn_unique = wx.Button(parent = self.panel, id = wx.ID_ANY,
  79. label = _("Get all values"))
  80. self.btn_unique.Enable(False)
  81. self.btn_uniquesample = wx.Button(parent = self.panel, id = wx.ID_ANY,
  82. label = _("Get sample"))
  83. self.btn_uniquesample.Enable(False)
  84. self.btn_lv = { 'is' : ['=', ],
  85. 'isnot' : ['!=', ],
  86. 'like' : ['LIKE', ],
  87. 'gt' : ['>', ],
  88. 'ge' : ['>=', ],
  89. 'lt' : ['<', ],
  90. 'le' : ['<=', ],
  91. 'or' : ['OR', ],
  92. 'not' : ['NOT', ],
  93. 'and' : ['AND', ],
  94. 'brac' : ['()', ],
  95. 'prc' : ['%', ] }
  96. for key, value in self.btn_lv.iteritems():
  97. btn = wx.Button(parent = self.panel, id = wx.ID_ANY,
  98. label = value[0])
  99. self.btn_lv[key].append(btn.GetId())
  100. #
  101. # text areas
  102. #
  103. self.text_sql = wx.TextCtrl(parent = self.panel, id = wx.ID_ANY,
  104. value = '', size = (-1, 75),
  105. style=wx.TE_MULTILINE)
  106. if self.qtype.lower() == "select":
  107. self.text_sql.SetValue("SELECT * FROM %s" % self.tablename)
  108. self.text_sql.SetInsertionPointEnd()
  109. wx.CallAfter(self.text_sql.SetFocus)
  110. #
  111. # list boxes (columns, values)
  112. #
  113. self.list_columns = wx.ListBox(parent = self.panel, id = wx.ID_ANY,
  114. choices = self.dbInfo.GetColumns(self.tablename),
  115. style = wx.LB_MULTIPLE)
  116. self.list_values = wx.ListBox(parent = self.panel, id = wx.ID_ANY,
  117. choices = self.colvalues,
  118. style = wx.LB_MULTIPLE)
  119. self.radio_cv = wx.RadioBox(parent = self.panel, id = wx.ID_ANY,
  120. label = " %s " % _("Add on double-click"),
  121. choices = [_("columns"), _("values")])
  122. self.radio_cv.SetSelection(1) # default 'values'
  123. self.close_onapply = wx.CheckBox(parent = self.panel, id = wx.ID_ANY,
  124. label = _("Close dialog on apply"))
  125. self.close_onapply.SetValue(True)
  126. #
  127. # bindings
  128. #
  129. self.btn_unique.Bind(wx.EVT_BUTTON, self.OnUniqueValues)
  130. self.btn_uniquesample.Bind(wx.EVT_BUTTON, self.OnSampleValues)
  131. for key, value in self.btn_lv.iteritems():
  132. self.FindWindowById(value[1]).Bind(wx.EVT_BUTTON, self.OnAddMark)
  133. self.btn_close.Bind(wx.EVT_BUTTON, self.OnClose)
  134. self.btn_clear.Bind(wx.EVT_BUTTON, self.OnClear)
  135. self.btn_verify.Bind(wx.EVT_BUTTON, self.OnVerify)
  136. self.btn_apply.Bind(wx.EVT_BUTTON, self.OnApply)
  137. self.list_columns.Bind(wx.EVT_LISTBOX, self.OnAddColumn)
  138. self.list_values.Bind(wx.EVT_LISTBOX, self.OnAddValue)
  139. self.text_sql.Bind(wx.EVT_TEXT, self.OnText)
  140. self.Bind(wx.EVT_CLOSE, self.OnClose)
  141. self.__doLayout()
  142. def __doLayout(self):
  143. """!Do dialog layout"""
  144. # dbInfo
  145. databasebox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
  146. label = " %s " % _("Database connection"))
  147. databaseboxsizer = wx.StaticBoxSizer(databasebox, wx.VERTICAL)
  148. databaseboxsizer.Add(item=dbm_base.createDbInfoDesc(self.panel, self.dbInfo, layer = self.layer),
  149. proportion=1,
  150. flag=wx.EXPAND | wx.ALL,
  151. border=3)
  152. # sql box
  153. sqlbox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
  154. label = " %s " % _("Query"))
  155. sqlboxsizer = wx.StaticBoxSizer(sqlbox, wx.VERTICAL)
  156. sqlboxsizer.Add(item = self.text_sql, flag = wx.EXPAND)
  157. pagesizer = wx.BoxSizer(wx.VERTICAL)
  158. # buttons
  159. buttonsizer = wx.FlexGridSizer(cols = 4, hgap = 5, vgap = 5)
  160. buttonsizer.Add(item = self.btn_clear)
  161. buttonsizer.Add(item = self.btn_verify)
  162. # buttonsizer1.Add(self.btn_help, (0,2))
  163. # buttonsizer1.Add(self.btn_load, (0,2))
  164. # buttonsizer1.Add(self.btn_save, (0,3))
  165. buttonsizer.Add(item = self.btn_apply)
  166. buttonsizer.Add(item = self.btn_close)
  167. buttonsizer2 = wx.GridBagSizer(5, 5)
  168. buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['is'][1]), pos = (0,0))
  169. buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['isnot'][1]), pos = (1,0))
  170. buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['like'][1]), pos = (2, 0))
  171. buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['gt'][1]), pos = (0, 1))
  172. buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['ge'][1]), pos = (1, 1))
  173. buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['or'][1]), pos = (2, 1))
  174. buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['lt'][1]), pos = (0, 2))
  175. buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['le'][1]), pos = (1, 2))
  176. buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['not'][1]), pos = (2, 2))
  177. buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['brac'][1]), pos = (0, 3))
  178. buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['prc'][1]), pos = (1, 3))
  179. buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['and'][1]), pos = (2, 3))
  180. buttonsizer3 = wx.BoxSizer(wx.HORIZONTAL)
  181. buttonsizer3.Add(item = self.btn_uniquesample, proportion = 0,
  182. flag = wx.ALIGN_CENTER_HORIZONTAL | wx.RIGHT, border = 5)
  183. buttonsizer3.Add(item = self.btn_unique, proportion = 0,
  184. flag = wx.ALIGN_CENTER_HORIZONTAL)
  185. radiosizer = wx.BoxSizer(wx.HORIZONTAL)
  186. radiosizer.Add(item = self.radio_cv, proportion = 1,
  187. flag = wx.ALIGN_CENTER_HORIZONTAL | wx.EXPAND, border = 5)
  188. hsizer = wx.BoxSizer(wx.HORIZONTAL)
  189. columnsbox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
  190. label = " %s " % _("Columns"))
  191. valuesbox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
  192. label = " %s " % _("Values"))
  193. # hsizer1.Add(wx.StaticText(self.panel,-1, "Unique values: "), border=0, proportion=1)
  194. columnsizer = wx.StaticBoxSizer(columnsbox, wx.VERTICAL)
  195. valuesizer = wx.StaticBoxSizer(valuesbox, wx.VERTICAL)
  196. columnsizer.Add(item = self.list_columns, proportion = 1,
  197. flag = wx.EXPAND)
  198. columnsizer.Add(item = radiosizer, proportion = 0,
  199. flag = wx.TOP | wx.EXPAND, border = 5)
  200. valuesizer.Add(item = self.list_values, proportion = 1,
  201. flag = wx.EXPAND)
  202. # self.list_columns.SetMinSize((-1,130))
  203. # self.list_values.SetMinSize((-1,100))
  204. valuesizer.Add(item = buttonsizer3, proportion = 0,
  205. flag = wx.TOP, border = 5)
  206. hsizer.Add(item = columnsizer, proportion = 1,
  207. flag = wx.EXPAND)
  208. hsizer.Add(item = valuesizer, proportion = 1,
  209. flag = wx.EXPAND)
  210. pagesizer.Add(item = databaseboxsizer,
  211. flag = wx.ALL | wx.EXPAND, border = 5)
  212. pagesizer.Add(item = hsizer, proportion = 1,
  213. flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 5)
  214. # pagesizer.Add(self.btn_uniqe,0,wx.ALIGN_LEFT|wx.TOP,border=5)
  215. # pagesizer.Add(self.btn_uniqesample,0,wx.ALIGN_LEFT|wx.TOP,border=5)
  216. pagesizer.Add(item = buttonsizer2, proportion = 0,
  217. flag = wx.ALIGN_CENTER_HORIZONTAL | wx.BOTTOM, border = 5)
  218. pagesizer.Add(item = sqlboxsizer, proportion = 0,
  219. flag = wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 5)
  220. pagesizer.Add(item = buttonsizer, proportion = 0,
  221. flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
  222. pagesizer.Add(item = self.close_onapply, proportion = 0,
  223. flag = wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 5)
  224. self.panel.SetAutoLayout(True)
  225. self.panel.SetSizer(pagesizer)
  226. pagesizer.Fit(self.panel)
  227. self.Layout()
  228. self.SetMinSize((660, 525))
  229. def OnUniqueValues(self, event, justsample = False):
  230. """!Get unique values"""
  231. vals = []
  232. try:
  233. idx = self.list_columns.GetSelections()[0]
  234. column = self.list_columns.GetString(idx)
  235. except:
  236. self.list_values.Clear()
  237. return
  238. self.list_values.Clear()
  239. querystring = "SELECT %s FROM %s" % (column, self.tablename)
  240. data = grass.db_select(table = self.tablename,
  241. sql = querystring,
  242. database = self.database,
  243. driver = self.driver)
  244. i = 0
  245. for line in data:
  246. if justsample and i < 256 or \
  247. not justsample:
  248. self.list_values.Append(line.strip())
  249. else:
  250. break
  251. i += 0
  252. def OnSampleValues(self, event):
  253. """!Get sample values"""
  254. self.OnUniqueValues(None, True)
  255. def OnAddColumn(self, event):
  256. """!Add column name to the query"""
  257. idx = self.list_columns.GetSelections()
  258. for i in idx:
  259. column = self.list_columns.GetString(i)
  260. self.__Add(element = 'column', value = column)
  261. if not self.btn_uniquesample.IsEnabled():
  262. self.btn_uniquesample.Enable(True)
  263. self.btn_unique.Enable(True)
  264. def OnAddValue(self, event):
  265. """!Add value"""
  266. idx = self.list_values.GetSelections()[0]
  267. value = self.list_values.GetString(idx)
  268. idx = self.list_columns.GetSelections()[0]
  269. column = self.list_columns.GetString(idx)
  270. ctype = self.dbInfo.GetTableDesc(self.dbInfo.GetTable(self.layer))[column]['type']
  271. if ctype == 'character':
  272. value = "'%s'" % value
  273. self.__Add(element = 'value', value = value)
  274. def OnAddMark(self, event):
  275. """!Add mark"""
  276. mark = None
  277. for key, value in self.btn_lv.iteritems():
  278. if event.GetId() == value[1]:
  279. mark = value[0]
  280. break
  281. self.__Add(element = 'mark', value = mark)
  282. def __Add(self, element, value):
  283. """!Add element to the query
  284. @param element element to add (column, value)
  285. """
  286. sqlstr = self.text_sql.GetValue()
  287. newsqlstr = ''
  288. if element == 'column':
  289. if self.radio_cv.GetSelection() == 0: # -> column
  290. idx1 = len('select')
  291. idx2 = sqlstr.lower().find('from')
  292. colstr = sqlstr[idx1:idx2].strip()
  293. if colstr == '*':
  294. cols = []
  295. else:
  296. cols = colstr.split(',')
  297. if value in cols:
  298. cols.remove(value)
  299. else:
  300. cols.append(value)
  301. if len(cols) < 1:
  302. cols = ['*',]
  303. newsqlstr = 'SELECT ' + ','.join(cols) + ' ' + sqlstr[idx2:]
  304. else: # -> where
  305. newsqlstr = sqlstr
  306. if sqlstr.lower().find('where') < 0:
  307. newsqlstr += ' WHERE'
  308. newsqlstr += ' ' + value
  309. elif element == 'value':
  310. newsqlstr = sqlstr + ' ' + value
  311. elif element == 'mark':
  312. newsqlstr = sqlstr + ' ' + value
  313. if newsqlstr:
  314. self.text_sql.SetValue(newsqlstr)
  315. def GetSQLStatement(self):
  316. """!Return SQL statement"""
  317. return self.text_sql.GetValue().strip().replace("\n"," ")
  318. def CloseOnApply(self):
  319. """!Return True if the dialog will be close on apply"""
  320. return self.close_onapply.IsChecked()
  321. def OnText(self, event):
  322. """Query string changed"""
  323. if len(self.text_sql.GetValue()) > 0:
  324. self.btn_verify.Enable(True)
  325. else:
  326. self.btn_verify.Enable(False)
  327. def OnApply(self, event):
  328. """Apply button pressed"""
  329. if self.evtHeader:
  330. self.evtHeader(event = 'apply')
  331. if self.close_onapply.IsChecked():
  332. self.Destroy()
  333. event.Skip()
  334. def OnVerify(self, event):
  335. """!Verify button pressed"""
  336. ret, msg = gcmd.RunCommand('db.select',
  337. getErrorMsg = True,
  338. table = self.tablename,
  339. sql = self.text_sql.GetValue(),
  340. flags = 't',
  341. driver = self.driver,
  342. database = self.database)
  343. if ret != 0 and msg:
  344. self.statusbar.SetStatusText(_("SQL statement is not valid"), 0)
  345. wx.MessageBox(parent=self,
  346. message=_("SQL statement is not valid.\n\n%s") % msg,
  347. caption=_("Warning"), style=wx.OK | wx.ICON_WARNING | wx.CENTRE)
  348. else:
  349. self.statusbar.SetStatusText(_("SQL statement is valid"), 0)
  350. def OnClear(self, event):
  351. """!Clear button pressed"""
  352. if self.qtype.lower() == "select":
  353. self.text_sql.SetValue("SELECT * FROM %s" % self.tablename)
  354. else:
  355. self.text_sql.SetValue("")
  356. def OnClose(self, event):
  357. """!Close button pressed"""
  358. if self.evtHeader:
  359. self.evtHeader(event = 'close')
  360. self.Destroy()
  361. event.Skip()
  362. if __name__ == "__main__":
  363. if len(sys.argv) != 2:
  364. print >>sys.stderr, __doc__
  365. sys.exit()
  366. app = wx.App(0)
  367. sqlb = SQLFrame(parent = None, title = _('SQL Builder'), vectmap = sys.argv[1])
  368. sqlb.Show()
  369. app.MainLoop()