GRASS Programmer's Manual  6.5.svn(2012)-r51648
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
sqlbuilder.py
Go to the documentation of this file.
00001 """!
00002 @package dbmgr.sqlbuilder
00003 
00004 @brief GRASS SQL Builder
00005 
00006 Classes:
00007  - sqlbuilder::SQLFrame
00008 
00009 Usage:
00010 @code
00011 python sqlbuilder.py vector_map
00012 @endcode
00013 
00014 (C) 2007-2009, 2011 by the GRASS Development Team
00015 
00016 This program is free software under the GNU General Public License
00017 (>=v2). Read the file COPYING that comes with GRASS for details.
00018 
00019 @author Jachym Cepicky <jachym.cepicky gmail.com> (original author)
00020 @author Martin Landa <landa.martin gmail.com>
00021 @author Hamish Bowman <hamish_b yahoo com>
00022 """
00023 
00024 import os
00025 import sys
00026 
00027 if __name__ == "__main__":
00028     sys.path.append(os.path.join(os.getenv('GISBASE'), 'etc', 'gui', 'wxpython'))
00029 from core import globalvar
00030 import wx
00031 
00032 from core.gcmd   import RunCommand, GError
00033 from dbmgr.vinfo import createDbInfoDesc, VectorDBInfo
00034 
00035 import grass.script as grass
00036 
00037 class SQLFrame(wx.Frame):
00038     """!SQL Frame class"""
00039     def __init__(self, parent, title, vectmap, id = wx.ID_ANY,
00040                  layer = 1, qtype = "select", evtheader = None):
00041         
00042         wx.Frame.__init__(self, parent, id, title)
00043         
00044         self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass_sql.ico'),
00045                              wx.BITMAP_TYPE_ICO))
00046         
00047         self.parent = parent
00048         self.evtHeader = evtheader
00049 
00050         #
00051         # variables
00052         #
00053         self.vectmap = vectmap # fullname
00054         if not "@" in self.vectmap:
00055             self.vectmap = grass.find_file(self.vectmap, element = 'vector')['fullname']
00056         self.mapname, self.mapset = self.vectmap.split("@", 1)
00057         
00058         # db info
00059         self.layer = layer
00060         self.dbInfo = VectorDBInfo(self.vectmap)
00061         self.tablename = self.dbInfo.GetTable(self.layer)
00062         self.driver, self.database = self.dbInfo.GetDbSettings(self.layer)
00063         
00064         self.qtype = qtype      # type of query: SELECT, UPDATE, DELETE, ...
00065         self.colvalues = []     # array with unique values in selected column
00066 
00067         # set dialog title
00068         self.SetTitle(_("GRASS SQL Builder (%(type)s): vector map <%(map)s>") % \
00069                           { 'type' : self.qtype.upper(), 'map' : self.vectmap })
00070         
00071         self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
00072 
00073         # statusbar
00074         self.statusbar = self.CreateStatusBar(number=1)
00075         self.statusbar.SetStatusText(_("SQL statement not verified"), 0)
00076        
00077         self._doLayout()
00078 
00079     def _doLayout(self):
00080         """!Do dialog layout"""
00081       
00082         pagesizer = wx.BoxSizer(wx.VERTICAL)
00083 
00084         
00085         # dbInfo
00086         databasebox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
00087                                    label = " %s " % _("Database connection"))
00088         databaseboxsizer = wx.StaticBoxSizer(databasebox, wx.VERTICAL)
00089         databaseboxsizer.Add(item=createDbInfoDesc(self.panel, self.dbInfo, layer = self.layer),
00090                              proportion=1,
00091                              flag=wx.EXPAND | wx.ALL,
00092                              border=3)
00093 
00094         #
00095         # text areas
00096         #
00097         # sql box
00098         sqlbox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
00099                               label = " %s " % _("Query"))
00100         sqlboxsizer = wx.StaticBoxSizer(sqlbox, wx.VERTICAL)
00101 
00102         self.text_sql = wx.TextCtrl(parent = self.panel, id = wx.ID_ANY,
00103                                     value = '', size = (-1, 50),
00104                                     style=wx.TE_MULTILINE)
00105         if self.qtype.lower() == "select":
00106             self.text_sql.SetValue("SELECT * FROM %s" % self.tablename)
00107         self.text_sql.SetInsertionPointEnd()
00108         self.text_sql.SetToolTipString(_("Example: %s") % "SELECT * FROM roadsmajor WHERE MULTILANE = 'no' OR OBJECTID < 10")
00109         wx.CallAfter(self.text_sql.SetFocus)
00110 
00111         sqlboxsizer.Add(item = self.text_sql, flag = wx.EXPAND)
00112         
00113         #
00114         # buttons
00115         #
00116         self.btn_clear  = wx.Button(parent = self.panel, id = wx.ID_CLEAR)
00117         self.btn_clear.SetToolTipString(_("Set SQL statement to default"))
00118         self.btn_verify = wx.Button(parent = self.panel, id = wx.ID_ANY,
00119                                     label = _("Verify"))
00120         self.btn_verify.SetToolTipString(_("Verify SQL statement"))
00121         self.btn_apply  = wx.Button(parent = self.panel, id = wx.ID_APPLY)
00122         self.btn_apply.SetToolTipString(_("Apply SQL statement and close the dialog"))
00123         self.btn_close  = wx.Button(parent = self.panel, id = wx.ID_CLOSE)
00124         self.btn_close.SetToolTipString(_("Close the dialog"))
00125         
00126         self.btn_lv = { 'is'    : ['=', ],
00127                         'isnot' : ['!=', ],
00128                         'like'  : ['LIKE', ],
00129                         'gt'    : ['>', ],
00130                         'ge'    : ['>=', ],
00131                         'lt'    : ['<', ],
00132                         'le'    : ['<=', ],
00133                         'or'    : ['OR', ],
00134                         'not'   : ['NOT', ],
00135                         'and'   : ['AND', ],
00136                         'brac'  : ['()', ],
00137                         'prc'   : ['%', ] }
00138         
00139         for key, value in self.btn_lv.iteritems():
00140             btn = wx.Button(parent = self.panel, id = wx.ID_ANY,
00141                             label = value[0])
00142             self.btn_lv[key].append(btn.GetId())
00143         
00144         buttonsizer = wx.FlexGridSizer(cols = 4, hgap = 5, vgap = 5)
00145         buttonsizer.Add(item = self.btn_clear)
00146         buttonsizer.Add(item = self.btn_verify)
00147         buttonsizer.Add(item = self.btn_apply)
00148         buttonsizer.Add(item = self.btn_close)
00149         
00150         buttonsizer2 = wx.GridBagSizer(5, 5)
00151         buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['is'][1]), pos = (0,0))
00152         buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['isnot'][1]), pos = (1,0))
00153         buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['like'][1]), pos = (2, 0))
00154 
00155         buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['gt'][1]), pos = (0, 1))
00156         buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['ge'][1]), pos = (1, 1))
00157         buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['or'][1]), pos = (2, 1))
00158 
00159         buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['lt'][1]), pos = (0, 2))
00160         buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['le'][1]), pos = (1, 2))
00161         buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['not'][1]), pos = (2, 2))
00162 
00163         buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['brac'][1]), pos = (0, 3))
00164         buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['prc'][1]), pos = (1, 3))
00165         buttonsizer2.Add(item = self.FindWindowById(self.btn_lv['and'][1]), pos = (2, 3))
00166         
00167         #
00168         # list boxes (columns, values)
00169         #
00170         hsizer = wx.BoxSizer(wx.HORIZONTAL)
00171         
00172         columnsbox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
00173                                   label = " %s " % _("Columns"))
00174         columnsizer = wx.StaticBoxSizer(columnsbox, wx.VERTICAL)
00175         self.list_columns = wx.ListBox(parent = self.panel, id = wx.ID_ANY,
00176                                        choices = self.dbInfo.GetColumns(self.tablename),
00177                                        style = wx.LB_MULTIPLE)
00178         columnsizer.Add(item = self.list_columns, proportion = 1,
00179                         flag = wx.EXPAND)
00180 
00181         radiosizer = wx.BoxSizer(wx.HORIZONTAL)
00182         self.radio_cv = wx.RadioBox(parent = self.panel, id = wx.ID_ANY,
00183                                     label = " %s " % _("Add on double-click"),
00184                                     choices = [_("columns"), _("values")])
00185         self.radio_cv.SetSelection(1) # default 'values'
00186         radiosizer.Add(item = self.radio_cv, proportion = 1,
00187                        flag = wx.ALIGN_CENTER_HORIZONTAL | wx.EXPAND, border = 5)
00188 
00189         columnsizer.Add(item = radiosizer, proportion = 0,
00190                         flag = wx.TOP | wx.EXPAND, border = 5)
00191         # self.list_columns.SetMinSize((-1,130))
00192         # self.list_values.SetMinSize((-1,100))
00193 
00194         valuesbox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
00195                                  label = " %s " % _("Values"))
00196         valuesizer = wx.StaticBoxSizer(valuesbox, wx.VERTICAL)
00197         self.list_values = wx.ListBox(parent = self.panel, id = wx.ID_ANY,
00198                                       choices = self.colvalues,
00199                                       style = wx.LB_MULTIPLE)
00200         valuesizer.Add(item = self.list_values, proportion = 1,
00201                        flag = wx.EXPAND)
00202         
00203         self.btn_unique = wx.Button(parent = self.panel, id = wx.ID_ANY,
00204                                     label = _("Get all values"))
00205         self.btn_unique.Enable(False)
00206         self.btn_uniquesample = wx.Button(parent = self.panel, id = wx.ID_ANY,
00207                                           label = _("Get sample"))
00208         self.btn_uniquesample.Enable(False)
00209 
00210         buttonsizer3 = wx.BoxSizer(wx.HORIZONTAL)
00211         buttonsizer3.Add(item = self.btn_uniquesample, proportion = 0,
00212                          flag = wx.ALIGN_CENTER_HORIZONTAL | wx.RIGHT, border = 5)
00213         buttonsizer3.Add(item = self.btn_unique, proportion = 0,
00214                          flag = wx.ALIGN_CENTER_HORIZONTAL)
00215 
00216         valuesizer.Add(item = buttonsizer3, proportion = 0,
00217                        flag = wx.TOP, border = 5)
00218         
00219         # hsizer1.Add(wx.StaticText(self.panel,-1, "Unique values: "), border=0, proportion=1)
00220  
00221         hsizer.Add(item = columnsizer, proportion = 1,
00222                    flag = wx.EXPAND)
00223         hsizer.Add(item = valuesizer, proportion = 1,
00224                    flag = wx.EXPAND)
00225         
00226         self.close_onapply = wx.CheckBox(parent = self.panel, id = wx.ID_ANY,
00227                                          label = _("Close dialog on apply"))
00228         self.close_onapply.SetValue(True)
00229  
00230         pagesizer.Add(item = databaseboxsizer,
00231                       flag = wx.ALL | wx.EXPAND, border = 5)
00232         pagesizer.Add(item = hsizer, proportion = 1,
00233                       flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 5)
00234         # pagesizer.Add(self.btn_uniqe,0,wx.ALIGN_LEFT|wx.TOP,border=5)
00235         # pagesizer.Add(self.btn_uniqesample,0,wx.ALIGN_LEFT|wx.TOP,border=5)
00236         pagesizer.Add(item = buttonsizer2, proportion = 0,
00237                       flag = wx.ALIGN_CENTER_HORIZONTAL)
00238         pagesizer.Add(item = sqlboxsizer, proportion = 0,
00239                       flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border = 5)
00240         pagesizer.Add(item = buttonsizer, proportion = 0,
00241                       flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
00242         pagesizer.Add(item = self.close_onapply, proportion = 0,
00243                       flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 5)
00244 
00245         #
00246         # bindings
00247         #
00248         self.btn_unique.Bind(wx.EVT_BUTTON,       self.OnUniqueValues)
00249         self.btn_uniquesample.Bind(wx.EVT_BUTTON, self.OnSampleValues)
00250         
00251         for key, value in self.btn_lv.iteritems():
00252             self.FindWindowById(value[1]).Bind(wx.EVT_BUTTON, self.OnAddMark)
00253         
00254         self.btn_close.Bind(wx.EVT_BUTTON,       self.OnClose)
00255         self.btn_clear.Bind(wx.EVT_BUTTON,       self.OnClear)
00256         self.btn_verify.Bind(wx.EVT_BUTTON,      self.OnVerify)
00257         self.btn_apply.Bind(wx.EVT_BUTTON,       self.OnApply)
00258 
00259         self.list_columns.Bind(wx.EVT_LISTBOX,   self.OnAddColumn)
00260         self.list_values.Bind(wx.EVT_LISTBOX,    self.OnAddValue)
00261         
00262         self.text_sql.Bind(wx.EVT_TEXT,          self.OnText)
00263         
00264         self.panel.SetAutoLayout(True)
00265         self.panel.SetSizer(pagesizer)
00266         pagesizer.Fit(self.panel)
00267         
00268         self.Layout()
00269         self.SetMinSize((660, 525))
00270         self.SetClientSize(self.panel.GetSize())
00271         self.CenterOnParent()
00272         
00273     def OnUniqueValues(self, event, justsample = False):
00274         """!Get unique values"""
00275         vals = []
00276         try:
00277             idx = self.list_columns.GetSelections()[0]
00278             column = self.list_columns.GetString(idx)
00279         except:
00280             self.list_values.Clear()
00281             return
00282         
00283         self.list_values.Clear()
00284         
00285         querystring = "SELECT %s FROM %s" % (column, self.tablename)
00286         
00287         data = grass.db_select(table = self.tablename,
00288                                sql = querystring,
00289                                database = self.database,
00290                                driver = self.driver)
00291         if not data:
00292             return
00293 
00294         desc = self.dbInfo.GetTableDesc(self.dbInfo.GetTable(self.layer))[column]
00295         
00296         i = 0
00297         for item in sorted(map(desc['ctype'], data)):
00298             if justsample and i < 256 or \
00299                not justsample:
00300                 if desc['type'] != 'character':
00301                     item = str(item)
00302                 self.list_values.Append(item)
00303             else:
00304                 break
00305             i += 1
00306         
00307     def OnSampleValues(self, event):
00308         """!Get sample values"""
00309         self.OnUniqueValues(None, True)
00310 
00311     def OnAddColumn(self, event):
00312         """!Add column name to the query"""
00313         idx = self.list_columns.GetSelections()
00314         for i in idx:
00315             column = self.list_columns.GetString(i)
00316             self._add(element = 'column', value = column)
00317         
00318         if not self.btn_uniquesample.IsEnabled():
00319             self.btn_uniquesample.Enable(True)
00320             self.btn_unique.Enable(True)
00321         
00322     def OnAddValue(self, event):
00323         """!Add value"""
00324         selection = self.list_values.GetSelections()
00325         if not selection:
00326             event.Skip()
00327             return
00328 
00329         idx = selection[0]
00330         value = self.list_values.GetString(idx)
00331         idx = self.list_columns.GetSelections()[0]
00332         column = self.list_columns.GetString(idx)
00333         
00334         ctype = self.dbInfo.GetTableDesc(self.dbInfo.GetTable(self.layer))[column]['type']
00335         
00336         if ctype == 'character':
00337             value = "'%s'" % value
00338         
00339         self._add(element = 'value', value = value)
00340 
00341     def OnAddMark(self, event):
00342         """!Add mark"""
00343         mark = None
00344         for key, value in self.btn_lv.iteritems():
00345             if event.GetId() == value[1]:
00346                 mark = value[0]
00347                 break
00348         
00349         self._add(element = 'mark', value = mark)
00350 
00351     def _add(self, element, value):
00352         """!Add element to the query
00353 
00354         @param element element to add (column, value)
00355         """
00356         sqlstr = self.text_sql.GetValue()
00357         newsqlstr = ''
00358         if element == 'column':
00359             if self.radio_cv.GetSelection() == 0: # -> column
00360                 idx1 = len('select')
00361                 idx2 = sqlstr.lower().find('from')
00362                 colstr = sqlstr[idx1:idx2].strip()
00363                 if colstr == '*':
00364                     cols = []
00365                 else:
00366                     cols = colstr.split(',')
00367                 if value in cols:
00368                         cols.remove(value)
00369                 else:
00370                     cols.append(value)
00371                 
00372                 if len(cols) < 1:
00373                     cols = ['*',]
00374                 
00375                 newsqlstr = 'SELECT ' + ','.join(cols) + ' ' + sqlstr[idx2:]
00376             else: # -> where
00377                 newsqlstr = sqlstr
00378                 if sqlstr.lower().find('where') < 0:
00379                     newsqlstr += ' WHERE'
00380                 
00381                 newsqlstr += ' ' + value
00382         
00383         elif element == 'value':
00384             newsqlstr = sqlstr + ' ' + value
00385         elif element == 'mark':
00386             newsqlstr = sqlstr + ' ' + value
00387         
00388         if newsqlstr:
00389             self.text_sql.SetValue(newsqlstr)
00390 
00391     def GetSQLStatement(self):
00392         """!Return SQL statement"""
00393         return self.text_sql.GetValue().strip().replace("\n"," ")
00394     
00395     def CloseOnApply(self):
00396         """!Return True if the dialog will be close on apply"""
00397         return self.close_onapply.IsChecked()
00398     
00399     def OnText(self, event):
00400         """Query string changed"""
00401         if len(self.text_sql.GetValue()) > 0:
00402             self.btn_verify.Enable(True)
00403         else:
00404             self.btn_verify.Enable(False)
00405         
00406     def OnApply(self, event):
00407         """Apply button pressed"""
00408         if self.evtHeader:
00409             self.evtHeader(event = 'apply')
00410 
00411         if self.close_onapply.IsChecked():
00412             self.Destroy()
00413             
00414         event.Skip()
00415     
00416     def OnVerify(self, event):
00417         """!Verify button pressed"""
00418         ret, msg = RunCommand('db.select',
00419                               getErrorMsg = True,
00420                               table = self.tablename,
00421                               sql = self.text_sql.GetValue(),
00422                               flags = 't',
00423                               driver = self.driver,
00424                               database = self.database)
00425         
00426         if ret != 0 and msg:
00427             self.statusbar.SetStatusText(_("SQL statement is not valid"), 0)
00428             GError(parent = self,
00429                    message = _("SQL statement is not valid.\n\n%s") % msg)
00430         else:
00431             self.statusbar.SetStatusText(_("SQL statement is valid"), 0)
00432                         
00433     def OnClear(self, event):
00434         """!Clear button pressed"""
00435         if self.qtype.lower() == "select":
00436             self.text_sql.SetValue("SELECT * FROM %s" % self.tablename)
00437         else:
00438             self.text_sql.SetValue("")
00439     
00440     def OnClose(self, event):
00441         """!Close button pressed"""
00442         if self.evtHeader:
00443             self.evtHeader(event = 'close')
00444         
00445         self.Destroy()
00446         
00447         event.Skip()
00448         
00449 if __name__ == "__main__":
00450     if len(sys.argv) != 2:
00451         print >>sys.stderr, __doc__
00452         sys.exit()
00453     
00454     import gettext
00455     gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode=True)
00456     
00457     app = wx.App(0)
00458     sqlb = SQLFrame(parent = None, title = _('SQL Builder'), vectmap = sys.argv[1])
00459     sqlb.Show()
00460     
00461     app.MainLoop()