sqlbuilder.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918
  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.ALIGN_CENTER_HORIZONTAL | 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.ALIGN_CENTER_HORIZONTAL | wx.RIGHT, border=5)
  224. buttonsizer3.Add(self.btn_unique, proportion=0,
  225. flag=wx.ALIGN_CENTER_HORIZONTAL)
  226. valuesizer.Add(buttonsizer3, proportion=0,
  227. flag=wx.TOP, border=5)
  228. # go to
  229. gotosizer = wx.BoxSizer(wx.HORIZONTAL)
  230. self.goto = TextCtrl(
  231. parent=self.valuespanel,
  232. id=wx.ID_ANY,
  233. style=wx.TE_PROCESS_ENTER)
  234. gotosizer.Add(StaticText(parent=self.valuespanel, id=wx.ID_ANY,
  235. label=_("Go to:")), proportion=0,
  236. flag=wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, border=5)
  237. gotosizer.Add(self.goto, proportion=1,
  238. flag=wx.EXPAND)
  239. valuesizer.Add(gotosizer, proportion=0,
  240. flag=wx.ALL | wx.EXPAND, border=5)
  241. self.hsizer.Add(columnsizer, proportion=1,
  242. flag=wx.EXPAND)
  243. self.hsizer.Add(self.valuespanel, proportion=1,
  244. flag=wx.EXPAND)
  245. self.close_onapply = wx.CheckBox(parent=self.panel, id=wx.ID_ANY,
  246. label=_("Close dialog on apply"))
  247. self.close_onapply.SetValue(True)
  248. if showDbInfo:
  249. self.pagesizer.Add(databaseboxsizer,
  250. flag=wx.ALL | wx.EXPAND, border=5)
  251. if modeChoices:
  252. self.pagesizer.Add(
  253. modesizer,
  254. proportion=0,
  255. flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
  256. border=5)
  257. self.pagesizer.Add(
  258. self.hsizer,
  259. proportion=1,
  260. flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
  261. border=5)
  262. # self.pagesizer.Add(self.btn_uniqe,0,wx.ALIGN_LEFT|wx.TOP,border=5)
  263. # self.pagesizer.Add(self.btn_uniqesample,0,wx.ALIGN_LEFT|wx.TOP,border=5)
  264. self.pagesizer.Add(self.btn_logicpanel, proportion=0,
  265. flag=wx.ALIGN_CENTER_HORIZONTAL)
  266. self.pagesizer.Add(sqlboxsizer, proportion=0,
  267. flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=5)
  268. self.pagesizer.Add(self.buttonsizer, proportion=0,
  269. flag=wx.ALIGN_RIGHT | wx.ALL, border=5)
  270. self.pagesizer.Add(
  271. self.close_onapply,
  272. proportion=0,
  273. flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
  274. border=5)
  275. #
  276. # bindings
  277. #
  278. if modeChoices:
  279. self.mode.Bind(wx.EVT_RADIOBOX, self.OnMode)
  280. # self.text_sql.Bind(wx.EVT_ACTIVATE, self.OnTextSqlActivate)TODO
  281. self.btn_unique.Bind(wx.EVT_BUTTON, self.OnUniqueValues)
  282. self.btn_uniquesample.Bind(wx.EVT_BUTTON, self.OnSampleValues)
  283. for key, value in six.iteritems(self.btn_logic):
  284. self.FindWindowById(value[1]).Bind(wx.EVT_BUTTON, self.OnAddMark)
  285. self.btn_close.Bind(wx.EVT_BUTTON, self.OnClose)
  286. self.btn_clear.Bind(wx.EVT_BUTTON, self.OnClear)
  287. self.btn_apply.Bind(wx.EVT_BUTTON, self.OnApply)
  288. self.list_columns.Bind(wx.EVT_LISTBOX, self.OnAddColumn)
  289. self.list_values.Bind(wx.EVT_LISTBOX, self.OnAddValue)
  290. self.goto.Bind(wx.EVT_TEXT, self.OnGoTo)
  291. self.goto.Bind(wx.EVT_TEXT_ENTER, self.OnAddValue)
  292. def OnUniqueValues(self, event, justsample=False):
  293. """Get unique values"""
  294. vals = []
  295. try:
  296. idx = self.list_columns.GetSelections()[0]
  297. column = self.list_columns.GetString(idx)
  298. except:
  299. self.list_values.Clear()
  300. return
  301. self.list_values.Clear()
  302. sql = "SELECT DISTINCT {column} FROM {table} ORDER BY {column}".format(
  303. column=column, table=self.tablename)
  304. if justsample:
  305. sql += " LIMIT {}".format(255)
  306. data = grass.db_select(
  307. sql=sql,
  308. database=self.database,
  309. driver=self.driver,
  310. sep='{_sep_}')
  311. if not data:
  312. return
  313. desc = self.dbInfo.GetTableDesc(
  314. self.dbInfo.GetTable(self.layer))[column]
  315. i = 0
  316. items = []
  317. for item in data: #sorted(set(map(lambda x: desc['ctype'](x[0]), data))):
  318. if desc['type'] not in ('character', 'text'):
  319. items.append(str(item[0]))
  320. else:
  321. items.append(u"'{}'".format(GetUnicodeValue(item[0])))
  322. i += 1
  323. self.list_values.AppendItems(items)
  324. def OnSampleValues(self, event):
  325. """Get sample values"""
  326. self.OnUniqueValues(None, True)
  327. def OnAddColumn(self, event):
  328. """Add column name to the query"""
  329. idx = self.list_columns.GetSelections()
  330. for i in idx:
  331. column = self.list_columns.GetString(i)
  332. self._add(element='column', value=column)
  333. if not self.btn_uniquesample.IsEnabled():
  334. self.btn_uniquesample.Enable(True)
  335. self.btn_unique.Enable(True)
  336. def OnAddValue(self, event):
  337. """Add value"""
  338. selection = self.list_values.GetSelections()
  339. if not selection:
  340. event.Skip()
  341. return
  342. idx = selection[0]
  343. value = self.list_values.GetString(idx)
  344. idx = self.list_columns.GetSelections()[0]
  345. column = self.list_columns.GetString(idx)
  346. ctype = self.dbInfo.GetTableDesc(
  347. self.dbInfo.GetTable(
  348. self.layer))[column]['type']
  349. self._add(element='value', value=value)
  350. def OnGoTo(self, event):
  351. # clear all previous selections
  352. for item in self.list_values.GetSelections():
  353. self.list_values.Deselect(item)
  354. gotoText = event.GetString()
  355. lenLimit = len(gotoText)
  356. found = idx = 0
  357. string = False
  358. for item in self.list_values.GetItems():
  359. if idx == 0 and item.startswith("'"):
  360. string = True
  361. if string:
  362. item = item[1:-1] # strip "'"
  363. if item[:lenLimit] == gotoText:
  364. found = idx
  365. break
  366. idx += 1
  367. if found > 0:
  368. self.list_values.SetSelection(found)
  369. def OnAddMark(self, event):
  370. """Add mark"""
  371. mark = None
  372. if self.btn_logicpanel and \
  373. self.btn_logicpanel.IsShown():
  374. btns = self.btn_logic
  375. elif self.btn_arithmeticpanel and \
  376. self.btn_arithmeticpanel.IsShown():
  377. btns = self.btn_arithmetic
  378. for key, value in six.iteritems(btns):
  379. if event.GetId() == value[1]:
  380. mark = value[0]
  381. break
  382. self._add(element='mark', value=mark)
  383. def GetSQLStatement(self):
  384. """Return SQL statement"""
  385. return self.text_sql.GetValue().strip().replace("\n", " ")
  386. def OnClose(self, event):
  387. self.Destroy()
  388. event.Skip()
  389. class SQLBuilderSelect(SQLBuilder):
  390. """Class for building SELECT SQL statement"""
  391. def __init__(self, parent, vectmap, id=wx.ID_ANY,
  392. layer=1, evtHandler=None):
  393. self.evtHandler = evtHandler
  394. # set dialog title
  395. title = _("GRASS SQL Builder (%(type)s) - <%(map)s>") % \
  396. {'type': "SELECT", 'map': vectmap}
  397. modeChoices = [_("Column to show (SELECT clause)"),
  398. _("Constraint for query (WHERE clause)")]
  399. SQLBuilder.__init__(self, parent, title, vectmap, id=wx.ID_ANY,
  400. modeChoices=modeChoices, layer=layer)
  401. def _doLayout(self, modeChoices):
  402. """Do dialog layout"""
  403. SQLBuilder._doLayout(self, modeChoices)
  404. self.text_sql.SetValue("SELECT * FROM %s" % self.tablename)
  405. self.text_sql.SetToolTip(
  406. _("Example: %s") %
  407. "SELECT * FROM roadsmajor WHERE MULTILANE = 'no' OR OBJECTID < 10")
  408. self.btn_verify = Button(parent=self.panel, id=wx.ID_ANY,
  409. label=_("Verify"))
  410. self.btn_verify.SetToolTip(_("Verify SQL statement"))
  411. self.buttonsizer.Insert(1, self.btn_verify)
  412. self.text_sql.Bind(wx.EVT_TEXT, self.OnText)
  413. self.btn_verify.Bind(wx.EVT_BUTTON, self.OnVerify)
  414. self.text_sql.SetInsertionPoint(self.text_sql.GetLastPosition())
  415. self.statusbar.SetStatusText(_("SQL statement not verified"), 0)
  416. def OnApply(self, event):
  417. """Apply button pressed"""
  418. if self.evtHandler:
  419. self.evtHandler(event='apply')
  420. if self.close_onapply.IsChecked():
  421. self.Destroy()
  422. event.Skip()
  423. def OnClear(self, event):
  424. """Clear button pressed"""
  425. self.text_sql.SetValue("SELECT * FROM %s" % self.tablename)
  426. def OnMode(self, event):
  427. """Adjusts builder for chosen mode"""
  428. if self.mode.GetSelection() == 0:
  429. self.valuespanel.Hide()
  430. self.btn_logicpanel.Hide()
  431. elif self.mode.GetSelection() == 1:
  432. self.valuespanel.Show()
  433. self.btn_logicpanel.Show()
  434. self.pagesizer.Layout()
  435. def OnText(self, event):
  436. """Query string changed"""
  437. if len(self.text_sql.GetValue()) > 0:
  438. self.btn_verify.Enable(True)
  439. else:
  440. self.btn_verify.Enable(False)
  441. def OnVerify(self, event):
  442. """Verify button pressed"""
  443. ret, msg = RunCommand('db.select',
  444. getErrorMsg=True,
  445. table=self.tablename,
  446. sql=self.text_sql.GetValue(),
  447. flags='t',
  448. driver=self.driver,
  449. database=self.database)
  450. if ret != 0 and msg:
  451. self.statusbar.SetStatusText(_("SQL statement is not valid"), 0)
  452. GError(parent=self,
  453. message=_("SQL statement is not valid.\n\n%s") % msg)
  454. else:
  455. self.statusbar.SetStatusText(_("SQL statement is valid"), 0)
  456. def _add(self, element, value):
  457. """Add element to the query
  458. :param element: element to add (column, value)
  459. """
  460. sqlstr = self.text_sql.GetValue()
  461. curspos = self.text_sql.GetInsertionPoint()
  462. newsqlstr = ''
  463. if element == 'column':
  464. if self.mode.GetSelection() == 0: # -> column
  465. idx1 = len('select')
  466. idx2 = sqlstr.lower().find('from')
  467. colstr = sqlstr[idx1:idx2].strip()
  468. if colstr == '*':
  469. cols = []
  470. else:
  471. cols = colstr.split(',')
  472. if value in cols:
  473. cols.remove(value)
  474. else:
  475. cols.append(value)
  476. if len(cols) < 1:
  477. cols = ['*', ]
  478. newsqlstr = 'SELECT ' + ','.join(cols) + ' '
  479. curspos = len(newsqlstr)
  480. newsqlstr += sqlstr[idx2:]
  481. else: # -> where
  482. newsqlstr = ''
  483. if sqlstr.lower().find('where') < 0:
  484. newsqlstr += ' WHERE'
  485. newsqlstr += ' ' + value
  486. curspos = self.text_sql.GetLastPosition() + len(newsqlstr)
  487. newsqlstr = sqlstr + newsqlstr
  488. elif element in ['value', 'mark']:
  489. addstr = ' ' + value + ' '
  490. newsqlstr = sqlstr[:curspos] + addstr + sqlstr[curspos:]
  491. curspos += len(addstr)
  492. if newsqlstr:
  493. self.text_sql.SetValue(newsqlstr)
  494. wx.CallAfter(self.text_sql.SetFocus)
  495. self.text_sql.SetInsertionPoint(curspos)
  496. def CloseOnApply(self):
  497. """Return True if the dialog will be close on apply"""
  498. return self.close_onapply.IsChecked()
  499. def OnClose(self, event):
  500. """Close button pressed"""
  501. if self.evtHandler:
  502. self.evtHandler(event='close')
  503. SQLBuilder.OnClose(self, event)
  504. class SQLBuilderUpdate(SQLBuilder):
  505. """Class for building UPDATE SQL statement"""
  506. def __init__(self, parent, vectmap, id=wx.ID_ANY,
  507. layer=1, column=None):
  508. self.column = column
  509. # set dialog title
  510. title = _("GRASS SQL Builder (%(type)s) - <%(map)s>") % \
  511. {'type': "UPDATE", 'map': vectmap}
  512. modeChoices = [_("Column to set (SET clause)"),
  513. _("Constraint for query (WHERE clause)"),
  514. _("Calculate column value to set")]
  515. SQLBuilder.__init__(self, parent, title, vectmap, id=wx.ID_ANY,
  516. modeChoices=modeChoices, layer=layer)
  517. # signals
  518. self.sqlApplied = Signal("SQLBuilder.sqlApplied")
  519. if parent: # TODO: replace by giface
  520. self.sqlApplied.connect(parent.Update)
  521. def _doLayout(self, modeChoices):
  522. """Do dialog layout"""
  523. SQLBuilder._doLayout(self, modeChoices)
  524. self.initText = "UPDATE %s SET" % self.tablename
  525. if self.column:
  526. self.initText += " %s = " % self.column
  527. self.text_sql.SetValue(self.initText)
  528. self.btn_arithmetic = {'eq': ['=', ],
  529. 'brac': ['()', ],
  530. 'plus': ['+', ],
  531. 'minus': ['-', ],
  532. 'divide': ['/', ],
  533. 'multiply': ['*', ]}
  534. self.btn_arithmeticpanel = wx.Panel(parent=self.panel, id=wx.ID_ANY)
  535. for key, value in six.iteritems(self.btn_arithmetic):
  536. btn = Button(parent=self.btn_arithmeticpanel, id=wx.ID_ANY,
  537. label=value[0])
  538. self.btn_arithmetic[key].append(btn.GetId())
  539. btn_arithmeticsizer = wx.GridBagSizer(hgap=5, vgap=5)
  540. btn_arithmeticsizer.Add(
  541. self.FindWindowById(
  542. self.btn_arithmetic['eq'][1]), pos=(
  543. 0, 0))
  544. btn_arithmeticsizer.Add(
  545. self.FindWindowById(
  546. self.btn_arithmetic['brac'][1]), pos=(
  547. 1, 0))
  548. btn_arithmeticsizer.Add(
  549. self.FindWindowById(
  550. self.btn_arithmetic['plus'][1]), pos=(
  551. 0, 1))
  552. btn_arithmeticsizer.Add(
  553. self.FindWindowById(
  554. self.btn_arithmetic['minus'][1]), pos=(
  555. 1, 1))
  556. btn_arithmeticsizer.Add(
  557. self.FindWindowById(
  558. self.btn_arithmetic['divide'][1]), pos=(
  559. 0, 2))
  560. btn_arithmeticsizer.Add(
  561. self.FindWindowById(
  562. self.btn_arithmetic['multiply'][1]), pos=(
  563. 1, 2))
  564. self.btn_arithmeticpanel.SetSizer(btn_arithmeticsizer)
  565. self.pagesizer.Insert(3, self.btn_arithmeticpanel,
  566. proportion=0, flag=wx.ALIGN_CENTER_HORIZONTAL)
  567. self.funcpanel = wx.Panel(parent=self.panel, id=wx.ID_ANY)
  568. self._initSqlFunctions()
  569. funcsbox = StaticBox(parent=self.funcpanel, id=wx.ID_ANY,
  570. label=" %s " % _("Functions"))
  571. funcsizer = wx.StaticBoxSizer(funcsbox, wx.VERTICAL)
  572. self.list_func = wx.ListBox(parent=self.funcpanel, id=wx.ID_ANY,
  573. choices=self.sqlFuncs['sqlite'].keys(),
  574. style=wx.LB_SORT)
  575. funcsizer.Add(self.list_func, proportion=1,
  576. flag=wx.EXPAND)
  577. self.funcpanel.SetSizer(funcsizer)
  578. self.hsizer.Insert(2, self.funcpanel,
  579. proportion=1, flag=wx.EXPAND)
  580. self.list_func.Bind(wx.EVT_LISTBOX, self.OnAddFunc)
  581. for key, value in six.iteritems(self.btn_arithmetic):
  582. self.FindWindowById(value[1]).Bind(wx.EVT_BUTTON, self.OnAddMark)
  583. self.mode.SetSelection(0)
  584. self.OnMode(None)
  585. self.text_sql.SetInsertionPoint(self.text_sql.GetLastPosition())
  586. def OnApply(self, event):
  587. """Apply button pressed"""
  588. ret, msg = RunCommand('db.execute',
  589. getErrorMsg=True,
  590. parent=self,
  591. stdin=self.text_sql.GetValue(),
  592. input='-',
  593. driver=self.driver,
  594. database=self.database)
  595. if ret != 0 and msg:
  596. self.statusbar.SetStatusText(_("SQL statement was not applied"), 0)
  597. else:
  598. self.statusbar.SetStatusText(_("SQL statement applied"), 0)
  599. self.sqlApplied.emit()
  600. def OnClear(self, event):
  601. """Clear button pressed"""
  602. self.text_sql.SetValue(self.initText)
  603. def OnMode(self, event):
  604. """Adjusts builder for chosen mode"""
  605. if self.mode.GetSelection() == 0:
  606. self.valuespanel.Hide()
  607. self.btn_logicpanel.Hide()
  608. self.btn_arithmeticpanel.Hide()
  609. self.funcpanel.Hide()
  610. elif self.mode.GetSelection() == 1:
  611. self.valuespanel.Show()
  612. self.btn_logicpanel.Show()
  613. self.btn_arithmeticpanel.Hide()
  614. self.funcpanel.Hide()
  615. elif self.mode.GetSelection() == 2:
  616. self.valuespanel.Hide()
  617. self.btn_logicpanel.Hide()
  618. self.btn_arithmeticpanel.Show()
  619. self.funcpanel.Show()
  620. self.pagesizer.Layout()
  621. def OnAddFunc(self, event):
  622. """Add function to the query"""
  623. if self.driver == 'dbf':
  624. GMessage(
  625. parent=self,
  626. message=_(
  627. "Dbf driver does not support usage of SQL functions."))
  628. return
  629. idx = self.list_func.GetSelections()
  630. for i in idx:
  631. func = self.sqlFuncs['sqlite'][self.list_func.GetString(i)][0]
  632. self._add(element='func', value=func)
  633. def _add(self, element, value):
  634. """Add element to the query
  635. :param element: element to add (column, value)
  636. """
  637. sqlstr = self.text_sql.GetValue()
  638. curspos = self.text_sql.GetInsertionPoint()
  639. newsqlstr = ''
  640. if element in ['value', 'mark', 'func'] or \
  641. (element == 'column' and self.mode.GetSelection() == 2):
  642. addstr = ' ' + value + ' '
  643. newsqlstr = sqlstr[:curspos] + addstr + sqlstr[curspos:]
  644. curspos += len(addstr)
  645. elif element == 'column':
  646. if self.mode.GetSelection() == 0: # -> column
  647. idx1 = sqlstr.lower().find('set') + len('set')
  648. idx2 = sqlstr.lower().find('where')
  649. if idx2 >= 0:
  650. colstr = sqlstr[idx1:idx2].strip()
  651. else:
  652. colstr = sqlstr[idx1:].strip()
  653. cols = [col.split('=')[0].strip() for col in colstr.split(',')]
  654. if unicode(value) in cols:
  655. self.text_sql.SetInsertionPoint(curspos)
  656. wx.CallAfter(self.text_sql.SetFocus)
  657. return
  658. if colstr:
  659. colstr += ','
  660. colstr = ' ' + colstr
  661. colstr += ' ' + value + '= '
  662. newsqlstr = sqlstr[:idx1] + colstr
  663. if idx2 >= 0:
  664. newsqlstr += sqlstr[idx2:]
  665. curspos = idx1 + len(colstr)
  666. elif self.mode.GetSelection() == 1: # -> where
  667. newsqlstr = ''
  668. if sqlstr.lower().find('where') < 0:
  669. newsqlstr += ' WHERE'
  670. newsqlstr += ' ' + value
  671. curspos = self.text_sql.GetLastPosition() + len(newsqlstr)
  672. newsqlstr = sqlstr + newsqlstr
  673. if newsqlstr:
  674. self.text_sql.SetValue(newsqlstr)
  675. wx.CallAfter(self.text_sql.SetFocus)
  676. self.text_sql.SetInsertionPoint(curspos)
  677. def _initSqlFunctions(self):
  678. self.sqlFuncs = {}
  679. # TODO add functions for other drivers
  680. self.sqlFuncs['sqlite'] = {
  681. 'ABS': ['ABS()'],
  682. 'LENGTH': ['LENGTH()'],
  683. 'LOWER': ['LOWER()'],
  684. 'LTRIM': ['LTRIM(,)'],
  685. 'MAX': ['MAX()'],
  686. 'MIN': ['MIN()'],
  687. 'RTRIM': ['RTRIM(,)'],
  688. 'SUBSTR': ['SUBSTR (,[,])'],
  689. 'TRIM': ['TRIM (,)']
  690. }
  691. class SQLBuilderWhere(SQLBuilder):
  692. """Class for building SELECT SQL WHERE statement"""
  693. def __init__(self, parent, vectmap, id=wx.ID_ANY,
  694. layer=1):
  695. title = _("GRASS SQL Builder (%(type)s) - <%(map)s>") % \
  696. {'type': "WHERE", 'map': vectmap}
  697. super(SQLBuilderWhere, self).__init__(
  698. parent, title, vectmap, id=wx.ID_ANY,
  699. layer=layer)
  700. def OnClear(self, event):
  701. self.text_sql.SetValue('')
  702. def OnApply(self, event):
  703. self.parent.SetValue(self.text_sql.GetValue())
  704. if self.close_onapply.IsChecked():
  705. self.Destroy()
  706. event.Skip()
  707. def _add(self, element, value):
  708. """Add element to the query
  709. :param element: element to add (column, value)
  710. """
  711. sqlstr = self.text_sql.GetValue()
  712. inspoint = self.text_sql.GetInsertionPoint()
  713. newsqlstr = ''
  714. if inspoint > 0 and sqlstr[inspoint-1] != ' ':
  715. newsqlstr += ' '
  716. newsqlstr += value
  717. if inspoint < len(sqlstr):
  718. newsqlstr += ' ' if sqlstr[inspoint] != ' ' else ''
  719. if newsqlstr:
  720. self.text_sql.SetValue(sqlstr[:inspoint] + newsqlstr + sqlstr[inspoint:])
  721. self.text_sql.SetInsertionPoint(inspoint + len(newsqlstr))
  722. wx.CallAfter(self.text_sql.SetFocus)
  723. if __name__ == "__main__":
  724. if len(sys.argv) not in [3, 4]:
  725. print(__doc__, file=sys.stderr)
  726. sys.exit()
  727. if len(sys.argv) == 3:
  728. layer = 1
  729. else:
  730. layer = int(sys.argv[3])
  731. if sys.argv[1] == 'select':
  732. sqlBuilder = SQLBuilderSelect
  733. elif sys.argv[1] == 'update':
  734. sqlBuilder = SQLBuilderUpdate
  735. else:
  736. print(__doc__, file=sys.stderr)
  737. sys.exit()
  738. app = wx.App(0)
  739. sqlb = sqlBuilder(parent=None, vectmap=sys.argv[2], layer=layer)
  740. sqlb.Show()
  741. app.MainLoop()