sqlbuilder.py 17 KB

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