sqlbuilder.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772
  1. """
  2. @package dbmgr.sqlbuilder
  3. @brief GRASS SQL Select/Update Builder
  4. Classes:
  5. - sqlbuilder::SQLBuilder
  6. - sqlbuilder::SQLBuilderSelect
  7. - sqlbuilder::SQLBuilderUpdate
  8. Usage:
  9. @code
  10. python sqlbuilder.py select|update vector_map
  11. @endcode
  12. (C) 2007-2014 by the GRASS Development Team
  13. This program is free software under the GNU General Public License
  14. (>=v2). Read the file COPYING that comes with GRASS 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. @author Refactoring, SQLBUilderUpdate by Stepan Turek <stepan.turek seznam.cz> (GSoC 2012, mentor: Martin Landa)
  19. """
  20. import os
  21. import sys
  22. from core import globalvar
  23. from core.utils import _
  24. import wx
  25. from grass.pydispatch.signal import Signal
  26. from core.gcmd import RunCommand, GError, GMessage
  27. from dbmgr.vinfo import CreateDbInfoDesc, VectorDBInfo, GetUnicodeValue
  28. import grass.script as grass
  29. class SQLBuilder(wx.Frame):
  30. """SQLBuider class
  31. Base class for classes, which builds SQL statements.
  32. """
  33. def __init__(self, parent, title, vectmap, modeChoices, id = wx.ID_ANY,
  34. layer = 1):
  35. wx.Frame.__init__(self, parent, id, title)
  36. self.SetIcon(wx.Icon(os.path.join(globalvar.ICONDIR, 'grass_sql.ico'),
  37. wx.BITMAP_TYPE_ICO))
  38. self.parent = parent
  39. # variables
  40. self.vectmap = vectmap # fullname
  41. if not "@" in self.vectmap:
  42. self.vectmap = grass.find_file(self.vectmap, element = 'vector')['fullname']
  43. if not self.vectmap:
  44. grass.fatal(_("Vector map <%s> not found") % vectmap)
  45. self.mapname, self.mapset = self.vectmap.split("@", 1)
  46. # db info
  47. self.layer = layer
  48. self.dbInfo = VectorDBInfo(self.vectmap)
  49. self.tablename = self.dbInfo.GetTable(self.layer)
  50. self.driver, self.database = self.dbInfo.GetDbSettings(self.layer)
  51. self.colvalues = [] # array with unique values in selected column
  52. self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
  53. # statusbar
  54. self.statusbar = self.CreateStatusBar(number=1)
  55. self._doLayout(modeChoices)
  56. def _doLayout(self, modeChoices):
  57. """Do dialog layout"""
  58. self.pagesizer = wx.BoxSizer(wx.VERTICAL)
  59. # dbInfo
  60. databasebox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
  61. label = " %s " % _("Database connection"))
  62. databaseboxsizer = wx.StaticBoxSizer(databasebox, wx.VERTICAL)
  63. databaseboxsizer.Add(item = CreateDbInfoDesc(self.panel, self.dbInfo, layer = self.layer),
  64. proportion=1,
  65. flag=wx.EXPAND | wx.ALL,
  66. border=3)
  67. #
  68. # text areas
  69. #
  70. # sql box
  71. sqlbox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
  72. label = " %s " % _("Query"))
  73. sqlboxsizer = wx.StaticBoxSizer(sqlbox, wx.VERTICAL)
  74. self.text_sql = wx.TextCtrl(parent = self.panel, id = wx.ID_ANY,
  75. value = '', size = (-1, 50),
  76. style=wx.TE_MULTILINE)
  77. self.text_sql.SetInsertionPointEnd()
  78. wx.CallAfter(self.text_sql.SetFocus)
  79. sqlboxsizer.Add(item = self.text_sql, flag = wx.EXPAND)
  80. #
  81. # buttons
  82. #
  83. self.btn_clear = wx.Button(parent = self.panel, id = wx.ID_CLEAR)
  84. self.btn_clear.SetToolTipString(_("Set SQL statement to default"))
  85. self.btn_apply = wx.Button(parent = self.panel, id = wx.ID_APPLY)
  86. self.btn_apply.SetToolTipString(_("Apply SQL statement in Attribute Table Manager"))
  87. self.btn_close = wx.Button(parent = self.panel, id = wx.ID_CLOSE)
  88. self.btn_close.SetToolTipString(_("Close the dialog"))
  89. self.btn_logic = { 'is' : ['=', ],
  90. 'isnot' : ['!=', ],
  91. 'like' : ['LIKE', ],
  92. 'gt' : ['>', ],
  93. 'ge' : ['>=', ],
  94. 'lt' : ['<', ],
  95. 'le' : ['<=', ],
  96. 'or' : ['OR', ],
  97. 'not' : ['NOT', ],
  98. 'and' : ['AND', ],
  99. 'brac' : ['()', ],
  100. 'prc' : ['%', ] }
  101. self.btn_logicpanel = wx.Panel(parent = self.panel, id = wx.ID_ANY)
  102. for key, value in self.btn_logic.iteritems():
  103. btn = wx.Button(parent = self.btn_logicpanel, id = wx.ID_ANY,
  104. label = value[0])
  105. self.btn_logic[key].append(btn.GetId())
  106. self.buttonsizer = wx.FlexGridSizer(cols = 4, hgap = 5, vgap = 5)
  107. self.buttonsizer.Add(item = self.btn_clear)
  108. self.buttonsizer.Add(item = self.btn_apply)
  109. self.buttonsizer.Add(item = self.btn_close)
  110. btn_logicsizer = wx.GridBagSizer(5, 5)
  111. btn_logicsizer.Add(item = self.FindWindowById(self.btn_logic['is'][1]), pos = (0,0))
  112. btn_logicsizer.Add(item = self.FindWindowById(self.btn_logic['isnot'][1]), pos = (1,0))
  113. btn_logicsizer.Add(item = self.FindWindowById(self.btn_logic['like'][1]), pos = (2, 0))
  114. btn_logicsizer.Add(item = self.FindWindowById(self.btn_logic['gt'][1]), pos = (0, 1))
  115. btn_logicsizer.Add(item = self.FindWindowById(self.btn_logic['ge'][1]), pos = (1, 1))
  116. btn_logicsizer.Add(item = self.FindWindowById(self.btn_logic['or'][1]), pos = (2, 1))
  117. btn_logicsizer.Add(item = self.FindWindowById(self.btn_logic['lt'][1]), pos = (0, 2))
  118. btn_logicsizer.Add(item = self.FindWindowById(self.btn_logic['le'][1]), pos = (1, 2))
  119. btn_logicsizer.Add(item = self.FindWindowById(self.btn_logic['not'][1]), pos = (2, 2))
  120. btn_logicsizer.Add(item = self.FindWindowById(self.btn_logic['brac'][1]), pos = (0, 3))
  121. btn_logicsizer.Add(item = self.FindWindowById(self.btn_logic['prc'][1]), pos = (1, 3))
  122. btn_logicsizer.Add(item = self.FindWindowById(self.btn_logic['and'][1]), pos = (2, 3))
  123. self.btn_logicpanel.SetSizer(btn_logicsizer)
  124. #
  125. # list boxes (columns, values)
  126. #
  127. self.hsizer = wx.BoxSizer(wx.HORIZONTAL)
  128. columnsbox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
  129. label = " %s " % _("Columns"))
  130. columnsizer = wx.StaticBoxSizer(columnsbox, wx.VERTICAL)
  131. self.list_columns = wx.ListBox(parent = self.panel, id = wx.ID_ANY,
  132. choices = self.dbInfo.GetColumns(self.tablename),
  133. style = wx.LB_MULTIPLE)
  134. columnsizer.Add(item = self.list_columns, proportion = 1,
  135. flag = wx.EXPAND)
  136. modesizer = wx.BoxSizer(wx.VERTICAL)
  137. self.mode = wx.RadioBox(parent = self.panel, id = wx.ID_ANY,
  138. label = " %s " % _("Interactive insertion"),
  139. choices = modeChoices,
  140. style = wx.RA_SPECIFY_COLS,
  141. majorDimension = 1)
  142. self.mode.SetSelection(1) # default 'values'
  143. modesizer.Add(item = self.mode, proportion = 1,
  144. flag = wx.ALIGN_CENTER_HORIZONTAL | wx.EXPAND, border = 5)
  145. # self.list_columns.SetMinSize((-1,130))
  146. # self.list_values.SetMinSize((-1,100))
  147. self.valuespanel = wx.Panel(parent = self.panel, id = wx.ID_ANY)
  148. valuesbox = wx.StaticBox(parent = self.valuespanel, id = wx.ID_ANY,
  149. label = " %s " % _("Values"))
  150. valuesizer = wx.StaticBoxSizer(valuesbox, wx.VERTICAL)
  151. self.list_values = wx.ListBox(parent = self.valuespanel, id = wx.ID_ANY,
  152. choices = self.colvalues,
  153. style = wx.LB_MULTIPLE)
  154. valuesizer.Add(item = self.list_values, proportion = 1,
  155. flag = wx.EXPAND)
  156. self.valuespanel.SetSizer(valuesizer)
  157. self.btn_unique = wx.Button(parent = self.valuespanel, id = wx.ID_ANY,
  158. label = _("Get all values"))
  159. self.btn_unique.Enable(False)
  160. self.btn_uniquesample = wx.Button(parent = self.valuespanel, id = wx.ID_ANY,
  161. label = _("Get sample"))
  162. self.btn_uniquesample.SetToolTipString(_("Get first 256 unique values as sample"))
  163. self.btn_uniquesample.Enable(False)
  164. buttonsizer3 = wx.BoxSizer(wx.HORIZONTAL)
  165. buttonsizer3.Add(item = self.btn_uniquesample, proportion = 0,
  166. flag = wx.ALIGN_CENTER_HORIZONTAL | wx.RIGHT, border = 5)
  167. buttonsizer3.Add(item = self.btn_unique, proportion = 0,
  168. flag = wx.ALIGN_CENTER_HORIZONTAL)
  169. valuesizer.Add(item = buttonsizer3, proportion = 0,
  170. flag = wx.TOP, border = 5)
  171. # go to
  172. gotosizer = wx.BoxSizer(wx.HORIZONTAL)
  173. self.goto = wx.TextCtrl(parent = self.valuespanel, id = wx.ID_ANY, style = wx.TE_PROCESS_ENTER)
  174. gotosizer.Add(item = wx.StaticText(parent = self.valuespanel, id = wx.ID_ANY,
  175. label = _("Go to:")), proportion = 0,
  176. flag = wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, border = 5)
  177. gotosizer.Add(item = self.goto, proportion = 1,
  178. flag = wx.EXPAND)
  179. valuesizer.Add(item = gotosizer, proportion = 0,
  180. flag = wx.ALL | wx.EXPAND, border = 5)
  181. self.hsizer.Add(item = columnsizer, proportion = 1,
  182. flag = wx.EXPAND)
  183. self.hsizer.Add(item = self.valuespanel, proportion = 1,
  184. flag = wx.EXPAND)
  185. self.close_onapply = wx.CheckBox(parent = self.panel, id = wx.ID_ANY,
  186. label = _("Close dialog on apply"))
  187. self.close_onapply.SetValue(True)
  188. self.pagesizer.Add(item = databaseboxsizer,
  189. flag = wx.ALL | wx.EXPAND, border = 5)
  190. self.pagesizer.Add(item = modesizer, proportion = 0,
  191. flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 5)
  192. self.pagesizer.Add(item = self.hsizer, proportion = 1,
  193. flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 5)
  194. # self.pagesizer.Add(self.btn_uniqe,0,wx.ALIGN_LEFT|wx.TOP,border=5)
  195. # self.pagesizer.Add(self.btn_uniqesample,0,wx.ALIGN_LEFT|wx.TOP,border=5)
  196. self.pagesizer.Add(item = self.btn_logicpanel, proportion = 0,
  197. flag = wx.ALIGN_CENTER_HORIZONTAL)
  198. self.pagesizer.Add(item = sqlboxsizer, proportion = 0,
  199. flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border = 5)
  200. self.pagesizer.Add(item = self.buttonsizer, proportion = 0,
  201. flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
  202. self.pagesizer.Add(item = self.close_onapply, proportion = 0,
  203. flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 5)
  204. #
  205. # bindings
  206. #
  207. self.mode.Bind(wx.EVT_RADIOBOX, self.OnMode)
  208. #self.text_sql.Bind(wx.EVT_ACTIVATE, self.OnTextSqlActivate)TODO
  209. self.btn_unique.Bind(wx.EVT_BUTTON, self.OnUniqueValues)
  210. self.btn_uniquesample.Bind(wx.EVT_BUTTON, self.OnSampleValues)
  211. for key, value in self.btn_logic.iteritems():
  212. self.FindWindowById(value[1]).Bind(wx.EVT_BUTTON, self.OnAddMark)
  213. self.btn_close.Bind(wx.EVT_BUTTON, self.OnClose)
  214. self.btn_clear.Bind(wx.EVT_BUTTON, self.OnClear)
  215. self.btn_apply.Bind(wx.EVT_BUTTON, self.OnApply)
  216. self.list_columns.Bind(wx.EVT_LISTBOX, self.OnAddColumn)
  217. self.list_values.Bind(wx.EVT_LISTBOX, self.OnAddValue)
  218. self.goto.Bind(wx.EVT_TEXT, self.OnGoTo)
  219. self.goto.Bind(wx.EVT_TEXT_ENTER, self.OnAddValue)
  220. self.panel.SetAutoLayout(True)
  221. self.panel.SetSizer(self.pagesizer)
  222. self.pagesizer.Fit(self.panel)
  223. self.Layout()
  224. self.SetMinSize((400, 600))
  225. self.SetClientSize(self.panel.GetSize())
  226. self.CenterOnParent()
  227. def OnUniqueValues(self, event, justsample = False):
  228. """Get unique values"""
  229. vals = []
  230. try:
  231. idx = self.list_columns.GetSelections()[0]
  232. column = self.list_columns.GetString(idx)
  233. except:
  234. self.list_values.Clear()
  235. return
  236. self.list_values.Clear()
  237. data = grass.db_select(sql = "SELECT %s FROM %s" % (column, self.tablename),
  238. database = self.database,
  239. driver = self.driver, sep = '{_sep_}')
  240. if not data:
  241. return
  242. desc = self.dbInfo.GetTableDesc(self.dbInfo.GetTable(self.layer))[column]
  243. i = 0
  244. for item in sorted(set(map(lambda x: desc['ctype'](x[0]), data))):
  245. if justsample and i > 255:
  246. break
  247. if desc['type'] != 'character':
  248. item = str(item)
  249. else:
  250. item = GetUnicodeValue(item)
  251. self.list_values.Append(item)
  252. i += 1
  253. def OnSampleValues(self, event):
  254. """Get sample values"""
  255. self.OnUniqueValues(None, True)
  256. def OnAddColumn(self, event):
  257. """Add column name to the query"""
  258. idx = self.list_columns.GetSelections()
  259. for i in idx:
  260. column = self.list_columns.GetString(i)
  261. self._add(element = 'column', value = column)
  262. if not self.btn_uniquesample.IsEnabled():
  263. self.btn_uniquesample.Enable(True)
  264. self.btn_unique.Enable(True)
  265. def OnAddValue(self, event):
  266. """Add value"""
  267. selection = self.list_values.GetSelections()
  268. if not selection:
  269. event.Skip()
  270. return
  271. idx = selection[0]
  272. value = self.list_values.GetString(idx)
  273. idx = self.list_columns.GetSelections()[0]
  274. column = self.list_columns.GetString(idx)
  275. ctype = self.dbInfo.GetTableDesc(self.dbInfo.GetTable(self.layer))[column]['type']
  276. if ctype == 'character':
  277. value = "'%s'" % value
  278. self._add(element = 'value', value = value)
  279. def OnGoTo(self, event):
  280. # clear all previous selections
  281. for item in self.list_values.GetSelections():
  282. self.list_values.Deselect(item)
  283. gotoText = event.GetString()
  284. lenLimit = len(gotoText)
  285. found = idx = 0
  286. for item in self.list_values.GetItems():
  287. if item[:lenLimit] == gotoText:
  288. found = idx
  289. break
  290. idx += 1
  291. if found > 0:
  292. self.list_values.SetSelection(found)
  293. def OnAddMark(self, event):
  294. """Add mark"""
  295. mark = None
  296. if self.btn_logicpanel and \
  297. self.btn_logicpanel.IsShown():
  298. btns = self.btn_logic
  299. elif self.btn_arithmeticpanel and \
  300. self.btn_arithmeticpanel.IsShown():
  301. btns = self.btn_arithmetic
  302. for key, value in btns.iteritems():
  303. if event.GetId() == value[1]:
  304. mark = value[0]
  305. break
  306. self._add(element = 'mark', value = mark)
  307. def GetSQLStatement(self):
  308. """Return SQL statement"""
  309. return self.text_sql.GetValue().strip().replace("\n"," ")
  310. def OnClose(self, event):
  311. self.Destroy()
  312. event.Skip()
  313. class SQLBuilderSelect(SQLBuilder):
  314. """Class for building SELECT SQL statement"""
  315. def __init__(self, parent, vectmap, id = wx.ID_ANY,
  316. layer = 1, evtHandler = None):
  317. self.evtHandler = evtHandler
  318. # set dialog title
  319. title = _("GRASS SQL Builder (%(type)s) - <%(map)s>") % \
  320. { 'type' : "SELECT", 'map' : vectmap }
  321. modeChoices = [_("Column to show (SELECT clause)"),
  322. _("Constraint for query (WHERE clause)")]
  323. SQLBuilder.__init__(self, parent, title, vectmap, id = wx.ID_ANY,
  324. modeChoices = modeChoices, layer = layer)
  325. def _doLayout(self, modeChoices):
  326. """Do dialog layout"""
  327. SQLBuilder._doLayout(self, modeChoices)
  328. self.text_sql.SetValue("SELECT * FROM %s" % self.tablename)
  329. self.text_sql.SetToolTipString(_("Example: %s") % "SELECT * FROM roadsmajor WHERE MULTILANE = 'no' OR OBJECTID < 10")
  330. self.btn_verify = wx.Button(parent = self.panel, id = wx.ID_ANY,
  331. label = _("Verify"))
  332. self.btn_verify.SetToolTipString(_("Verify SQL statement"))
  333. self.buttonsizer.Insert(item = self.btn_verify, before = 1)
  334. self.text_sql.Bind(wx.EVT_TEXT, self.OnText)
  335. self.btn_verify.Bind(wx.EVT_BUTTON, self.OnVerify)
  336. self.text_sql.SetInsertionPoint(self.text_sql.GetLastPosition())
  337. self.statusbar.SetStatusText(_("SQL statement not verified"), 0)
  338. def OnApply(self, event):
  339. """Apply button pressed"""
  340. if self.evtHandler:
  341. self.evtHandler(event = 'apply')
  342. if self.close_onapply.IsChecked():
  343. self.Destroy()
  344. event.Skip()
  345. def OnClear(self, event):
  346. """Clear button pressed"""
  347. self.text_sql.SetValue("SELECT * FROM %s" % self.tablename)
  348. def OnMode(self, event):
  349. """Adjusts builder for chosen mode"""
  350. if self.mode.GetSelection() == 0:
  351. self.valuespanel.Hide()
  352. self.btn_logicpanel.Hide()
  353. elif self.mode.GetSelection() == 1:
  354. self.valuespanel.Show()
  355. self.btn_logicpanel.Show()
  356. self.pagesizer.Layout()
  357. def OnText(self, event):
  358. """Query string changed"""
  359. if len(self.text_sql.GetValue()) > 0:
  360. self.btn_verify.Enable(True)
  361. else:
  362. self.btn_verify.Enable(False)
  363. def OnVerify(self, event):
  364. """Verify button pressed"""
  365. ret, msg = RunCommand('db.select',
  366. getErrorMsg = True,
  367. table = self.tablename,
  368. sql = self.text_sql.GetValue(),
  369. flags = 't',
  370. driver = self.driver,
  371. database = self.database)
  372. if ret != 0 and msg:
  373. self.statusbar.SetStatusText(_("SQL statement is not valid"), 0)
  374. GError(parent = self,
  375. message = _("SQL statement is not valid.\n\n%s") % msg)
  376. else:
  377. self.statusbar.SetStatusText(_("SQL statement is valid"), 0)
  378. def _add(self, element, value):
  379. """Add element to the query
  380. :param element: element to add (column, value)
  381. """
  382. sqlstr = self.text_sql.GetValue()
  383. curspos = self.text_sql.GetInsertionPoint()
  384. newsqlstr = ''
  385. if element == 'column':
  386. if self.mode.GetSelection() == 0: # -> column
  387. idx1 = len('select')
  388. idx2 = sqlstr.lower().find('from')
  389. colstr = sqlstr[idx1:idx2].strip()
  390. if colstr == '*':
  391. cols = []
  392. else:
  393. cols = colstr.split(',')
  394. if value in cols:
  395. cols.remove(value)
  396. else:
  397. cols.append(value)
  398. if len(cols) < 1:
  399. cols = ['*',]
  400. newsqlstr = 'SELECT ' + ','.join(cols) + ' '
  401. curspos = len(newsqlstr)
  402. newsqlstr += sqlstr[idx2:]
  403. else: # -> where
  404. newsqlstr = ''
  405. if sqlstr.lower().find('where') < 0:
  406. newsqlstr += ' WHERE'
  407. newsqlstr += ' ' + value
  408. curspos = self.text_sql.GetLastPosition() + len(newsqlstr)
  409. newsqlstr = sqlstr + newsqlstr
  410. elif element in ['value', 'mark']:
  411. addstr = ' ' + value + ' '
  412. newsqlstr = sqlstr[:curspos] + addstr + sqlstr[curspos:]
  413. curspos += len(addstr)
  414. if newsqlstr:
  415. self.text_sql.SetValue(newsqlstr)
  416. wx.CallAfter(self.text_sql.SetFocus)
  417. self.text_sql.SetInsertionPoint(curspos)
  418. def CloseOnApply(self):
  419. """Return True if the dialog will be close on apply"""
  420. return self.close_onapply.IsChecked()
  421. def OnClose(self, event):
  422. """Close button pressed"""
  423. if self.evtHandler:
  424. self.evtHandler(event = 'close')
  425. SQLBuilder.OnClose(self, event)
  426. class SQLBuilderUpdate(SQLBuilder):
  427. """Class for building UPDATE SQL statement"""
  428. def __init__(self, parent, vectmap, id = wx.ID_ANY,
  429. layer = 1, column = None):
  430. self.column = column
  431. # set dialog title
  432. title = _("GRASS SQL Builder (%(type)s) - <%(map)s>") % \
  433. { 'type' : "UPDATE", 'map' : vectmap }
  434. modeChoices = [_("Column to set (SET clause)"),
  435. _("Constraint for query (WHERE clause)"),
  436. _("Calculate column value to set")]
  437. SQLBuilder.__init__(self, parent, title, vectmap, id = wx.ID_ANY,
  438. modeChoices = modeChoices, layer = layer)
  439. # signals
  440. self.sqlApplied = Signal("SQLBuilder.sqlApplied")
  441. if parent: # TODO: replace by giface
  442. self.sqlApplied.connect(parent.Update)
  443. def _doLayout(self, modeChoices):
  444. """Do dialog layout"""
  445. SQLBuilder._doLayout(self, modeChoices)
  446. self.initText = "UPDATE %s SET" % self.tablename
  447. if self.column:
  448. self.initText += " %s = " % self.column
  449. self.text_sql.SetValue(self.initText)
  450. self.btn_arithmetic = { 'eq' : ['=', ],
  451. 'brac' : ['()',],
  452. 'plus' : ['+', ],
  453. 'minus' : ['-', ],
  454. 'divide' : ['/', ],
  455. 'multiply' : ['*', ]}
  456. self.btn_arithmeticpanel = wx.Panel(parent = self.panel, id = wx.ID_ANY)
  457. for key, value in self.btn_arithmetic.iteritems():
  458. btn = wx.Button(parent = self.btn_arithmeticpanel, id = wx.ID_ANY,
  459. label = value[0])
  460. self.btn_arithmetic[key].append(btn.GetId())
  461. btn_arithmeticsizer = wx.GridBagSizer(hgap = 5, vgap = 5)
  462. btn_arithmeticsizer.Add(item = self.FindWindowById(self.btn_arithmetic['eq'][1]), pos = (0, 0))
  463. btn_arithmeticsizer.Add(item = self.FindWindowById(self.btn_arithmetic['brac'][1]), pos = (1, 0))
  464. btn_arithmeticsizer.Add(item = self.FindWindowById(self.btn_arithmetic['plus'][1]), pos = (0, 1))
  465. btn_arithmeticsizer.Add(item = self.FindWindowById(self.btn_arithmetic['minus'][1]), pos = (1, 1))
  466. btn_arithmeticsizer.Add(item = self.FindWindowById(self.btn_arithmetic['divide'][1]), pos = (0, 2))
  467. btn_arithmeticsizer.Add(item = self.FindWindowById(self.btn_arithmetic['multiply'][1]), pos = (1, 2))
  468. self.btn_arithmeticpanel.SetSizer(btn_arithmeticsizer)
  469. self.pagesizer.Insert(item = self.btn_arithmeticpanel, before = 3,
  470. proportion = 0, flag = wx.ALIGN_CENTER_HORIZONTAL)
  471. self.funcpanel = wx.Panel(parent = self.panel, id = wx.ID_ANY)
  472. self._initSqlFunctions()
  473. funcsbox = wx.StaticBox(parent = self.funcpanel, id = wx.ID_ANY,
  474. label = " %s " % _("Functions"))
  475. funcsizer = wx.StaticBoxSizer(funcsbox, wx.VERTICAL)
  476. self.list_func = wx.ListBox(parent = self.funcpanel, id = wx.ID_ANY,
  477. choices = self.sqlFuncs['sqlite'].keys(),
  478. style = wx.LB_SORT)
  479. funcsizer.Add(item = self.list_func, proportion = 1,
  480. flag = wx.EXPAND)
  481. self.funcpanel.SetSizer(funcsizer)
  482. self.hsizer.Insert(item = self.funcpanel, before = 2,
  483. proportion = 1, flag = wx.EXPAND)
  484. self.list_func.Bind(wx.EVT_LISTBOX, self.OnAddFunc)
  485. for key, value in self.btn_arithmetic.iteritems():
  486. self.FindWindowById(value[1]).Bind(wx.EVT_BUTTON, self.OnAddMark)
  487. self.mode.SetSelection(0)
  488. self.OnMode(None)
  489. self.text_sql.SetInsertionPoint(self.text_sql.GetLastPosition())
  490. def OnApply(self, event):
  491. """Apply button pressed"""
  492. ret, msg = RunCommand('db.execute',
  493. getErrorMsg = True,
  494. parent = self,
  495. stdin = self.text_sql.GetValue(),
  496. input = '-',
  497. driver = self.driver,
  498. database = self.database)
  499. if ret != 0 and msg:
  500. self.statusbar.SetStatusText(_("SQL statement was not applied"), 0)
  501. else:
  502. self.statusbar.SetStatusText(_("SQL statement applied"), 0)
  503. self.sqlApplied.emit()
  504. def OnClear(self, event):
  505. """Clear button pressed"""
  506. self.text_sql.SetValue(self.initText)
  507. def OnMode(self, event):
  508. """Adjusts builder for chosen mode"""
  509. if self.mode.GetSelection() == 0:
  510. self.valuespanel.Hide()
  511. self.btn_logicpanel.Hide()
  512. self.btn_arithmeticpanel.Hide()
  513. self.funcpanel.Hide()
  514. elif self.mode.GetSelection() == 1:
  515. self.valuespanel.Show()
  516. self.btn_logicpanel.Show()
  517. self.btn_arithmeticpanel.Hide()
  518. self.funcpanel.Hide()
  519. elif self.mode.GetSelection() == 2:
  520. self.valuespanel.Hide()
  521. self.btn_logicpanel.Hide()
  522. self.btn_arithmeticpanel.Show()
  523. self.funcpanel.Show()
  524. self.pagesizer.Layout()
  525. def OnAddFunc(self, event):
  526. """Add function to the query"""
  527. if self.driver == 'dbf':
  528. GMessage(parent = self,
  529. message = _("Dbf driver does not support usage of SQL functions."))
  530. return
  531. idx = self.list_func.GetSelections()
  532. for i in idx:
  533. func = self.sqlFuncs['sqlite'][self.list_func.GetString(i)][0]
  534. self._add(element = 'func', value = func)
  535. def _add(self, element, value):
  536. """Add element to the query
  537. :param element: element to add (column, value)
  538. """
  539. sqlstr = self.text_sql.GetValue()
  540. curspos = self.text_sql.GetInsertionPoint()
  541. newsqlstr = ''
  542. if element in ['value', 'mark', 'func'] or \
  543. (element == 'column' and self.mode.GetSelection() == 2):
  544. addstr = ' ' + value + ' '
  545. newsqlstr = sqlstr[:curspos] + addstr + sqlstr[curspos:]
  546. curspos += len(addstr)
  547. elif element == 'column':
  548. if self.mode.GetSelection() == 0: # -> column
  549. idx1 = sqlstr.lower().find('set') + len('set')
  550. idx2 = sqlstr.lower().find('where')
  551. if idx2 >= 0:
  552. colstr = sqlstr[idx1:idx2].strip()
  553. else:
  554. colstr = sqlstr[idx1:].strip()
  555. cols = [col.split('=')[0].strip() for col in colstr.split(',')]
  556. if unicode(value) in cols:
  557. self.text_sql.SetInsertionPoint(curspos)
  558. wx.CallAfter(self.text_sql.SetFocus)
  559. return
  560. if colstr:
  561. colstr += ','
  562. colstr = ' ' + colstr
  563. colstr += ' ' + value + '= '
  564. newsqlstr = sqlstr[:idx1] + colstr
  565. if idx2 >= 0:
  566. newsqlstr += sqlstr[idx2:]
  567. curspos = idx1 + len(colstr)
  568. elif self.mode.GetSelection() == 1: # -> where
  569. newsqlstr = ''
  570. if sqlstr.lower().find('where') < 0:
  571. newsqlstr += ' WHERE'
  572. newsqlstr += ' ' + value
  573. curspos = self.text_sql.GetLastPosition() + len(newsqlstr)
  574. newsqlstr = sqlstr + newsqlstr
  575. if newsqlstr:
  576. self.text_sql.SetValue(newsqlstr)
  577. wx.CallAfter(self.text_sql.SetFocus)
  578. self.text_sql.SetInsertionPoint(curspos)
  579. def _initSqlFunctions(self):
  580. self.sqlFuncs = {}
  581. # TODO add functions for other drivers
  582. self.sqlFuncs['sqlite'] = {
  583. 'ABS' : ['ABS()'],
  584. 'LENGTH' : ['LENGTH()'],
  585. 'LOWER' : ['LOWER()'],
  586. 'LTRIM' : ['LTRIM(,)'],
  587. 'MAX' : ['MAX()'],
  588. 'MIN' : ['MIN()'],
  589. 'RTRIM' : ['RTRIM(,)'],
  590. 'SUBSTR' : ['SUBSTR (,[,])'],
  591. 'TRIM' : ['TRIM (,)']
  592. }
  593. if __name__ == "__main__":
  594. if len(sys.argv) not in [3, 4]:
  595. print >>sys.stderr, __doc__
  596. sys.exit()
  597. if len(sys.argv) == 3:
  598. layer = 1
  599. else:
  600. layer = int(sys.argv[3])
  601. if sys.argv[1] == 'select':
  602. sqlBuilder = SQLBuilderSelect
  603. elif sys.argv[1] == 'update':
  604. sqlBuilder = SQLBuilderUpdate
  605. else:
  606. print >>sys.stderr, __doc__
  607. sys.exit()
  608. app = wx.App(0)
  609. sqlb = sqlBuilder(parent = None, vectmap = sys.argv[2], layer = layer)
  610. sqlb.Show()
  611. app.MainLoop()