sqlbuilder.py 31 KB

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