sqlbuilder.py 30 KB

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