sqlbuilder.py 17 KB

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