sqlbuilder.py 31 KB

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