sqlbuilder.py 29 KB

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