Repository: biolab/orange3

Issue:
SQL DB access error using widgets in Orange 3.13
<!--
This is an issue template. Please fill in the relevant details in the
sections below.

Wrap code and verbatim terminal window output into triple backticks, see:
https://help.github.com/articles/basic-writing-and-formatting-syntax/#quoting-code

```
Exception:
Orange.data.sql.backend.base.BackendError: (156, b"Incorrect syntax near the keyword 'SELECT'.DB-Lib error message 20018, severity 15:\nGeneral SQL Server error: Check messages from the SQL Server\nDB-Lib error message 20018, severity 15:\nGeneral SQL Server error: Check messages from the SQL Server\n")
Module:
Orange.data.sql.backend.mssql:79
Widget Name:
Data Sampler
Widget Module:
Orange.widgets.data.owdatasampler:193
Widget Scheme:
C:\Users\S\AppData\Local\Temp\ows-5z1kwe6e.ows.xml
Version:
3.13.0
Environment:
Python 3.6.5 on Windows 10 10.0.16299 AMD64
Installed Packages:
AnyQt==0.0.8, Babel==2.5.3, Bottleneck==1.2.1, CommonMark==0.7.5, Jinja2==2.10, MarkupSafe==1.0, Orange3-Timeseries==0.3.3, Orange3==3.13.0, Pillow==5.1.0, PyOpenGL==3.1.1a1, PySocks==1.6.8, PyWavelets==0.5.2, PyYAML==3.12, Pygments==2.2.0, QtAwesome==0.4.4, QtPy==1.4.0, Send2Trash==1.5.0, Sphinx==1.7.2, alabaster==0.7.10, asn1crypto==0.24.0, astroid==1.6.2, astropy==3.0.1, attrs==17.4.0, backcall==0.1.0, bleach==2.1.3, bokeh==0.12.16, certifi==2018.4.16, cffi==1.11.5, chardet==3.0.4, click==6.7, cloudpickle==0.5.2, colorama==0.3.9, cryptography==2.2.2, cycler==0.10.0, cytoolz==0.9.0.1, dask==0.17.2, decorator==4.2.1, dill==0.2.7.1, distributed==1.21.5, docutils==0.14, entrypoints==0.2.3, fast-histogram==0.4, future==0.16.0, glue-core==0.13.3, glue-vispy-viewers==0.10, h5py==2.7.1, heapdict==1.0.0, html5lib==1.0.1, idna==2.6, imageio==2.3.0, imagesize==1.0.0, ipykernel==4.8.2, ipython-genutils==0.2.0, ipython==6.4.0, isort==4.3.4, jedi==0.12.0, joblib==0.11, jsonschema==2.6.0, jupyter-client==5.2.3, jupyter-core==4.4.0, jupyterlab-launcher==0.10.5, jupyterlab==0.32.1, keyring==12.0.1, keyrings.alt==3.0, kiwisolver==1.0.1, lazy-object-proxy==1.3.1, locket==0.2.0, lxml==4.2.1, matplotlib==2.2.2, mccabe==0.6.1, mistune==0.8.3, mkl-fft==1.0.0, mkl-random==1.0.1, more-itertools==4.1.0, mpl-scatter-density==0.3, msgpack-python==0.5.6, nbconvert==5.3.1, nbformat==4.4.0, networkx==2.1, notebook==5.5.0, numpy==1.13.3, numpydoc==0.8.0, olefile==0.45.1, packaging==17.1, pandas-datareader==0.6.0, pandas==0.23.0, pandocfilters==1.4.2, parso==0.1.1, partd==0.3.8, pickleshare==0.7.4, pip==10.0.1, plotly==2.6.0, pluggy==0.6.0, prompt-toolkit==1.0.15, psutil==5.4.3, psycopg2==2.7.4, py==1.5.3, pyOpenSSL==17.5.0, pycodestyle==2.4.0, pycparser==2.18, pyflakes==1.6.0, pylint==1.8.4, pymssql==2.1.3, pyparsing==2.2.0, pyqtgraph==0.10.0, pyreadline==2.1, pytest-arraydiff==0.2, pytest-astropy==0.2.1, pytest-doctestplus==0.1.2, pytest-openfiles==0.2.0, pytest-remotedata==0.2.0, pytest==3.5.0, python-dateutil==2.7.2, pytz==2018.3, pywin32==223, pywinpty==0.5.1, pyzmq==17.0.0, qtconsole==4.3.1, requests-file==1.4.3, requests-ftp==0.3.1, requests==2.18.4, rope==0.10.7, scikit-image==0.13.1, scikit-learn==0.19.1, scipy==1.0.1, serverfiles==0.2.1, setuptools==39.0.1, simplegeneric==0.8.1, six==1.11.0, snowballstemmer==1.2.1, sortedcontainers==1.5.9, sphinxcontrib-websupport==1.0.1, spyder==3.2.8, statsmodels==0.9.0, tblib==1.3.2, terminado==0.8.1, testpath==0.3.1, toolz==0.9.0, tornado==5.0.1, traitlets==4.3.2, typing==3.6.4, urllib3==1.22, wcwidth==0.1.7, webencodings==0.5.1, wheel==0.31.0, win-inet-pton==1.0.1, wincertstore==0.2, wrapt==1.10.11, xlrd==1.1.0, zict==0.1.3
Machine ID:
57455341202609
Stack Trace:
Traceback (most recent call last):
  File "pymssql.pyx", line 447, in pymssql.Cursor.execute (pymssql.c:7119)
  File "_mssql.pyx", line 1011, in _mssql.MSSQLConnection.execute_query (_mssql.c:11586)
  File "_mssql.pyx", line 1042, in _mssql.MSSQLConnection.execute_query (_mssql.c:11466)
  File "_mssql.pyx", line 1175, in _mssql.MSSQLConnection.format_and_run_query (_mssql.c:12746)
  File "_mssql.pyx", line 1586, in _mssql.check_cancel_and_raise (_mssql.c:16880)
  File "_mssql.pyx", line 1630, in _mssql.maybe_raise_MSSQLDatabaseException (_mssql.c:17524)
_mssql.MSSQLDatabaseException: (156, b"Incorrect syntax near the keyword 'SELECT'.DB-Lib error message 20018, severity 15:\nGeneral SQL Server error: Check messages from the SQL Server\nDB-Lib error message 20018, severity 15:\nGeneral SQL Server error: Check messages from the SQL Server\n")

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\s\AppData\Local\Continuum\anaconda3\envs\RStudio\lib\site-packages\Orange\data\sql\backend\mssql.py", line 76, in execute_sql_query
    cur.execute(query, *params)
  File "pymssql.pyx", line 467, in pymssql.Cursor.execute (pymssql.c:7561)
pymssql.OperationalError: (156, b"Incorrect syntax near the keyword 'SELECT'.DB-Lib error message 20018, severity 15:\nGeneral SQL Server error: Check messages from the SQL Server\nDB-Lib error message 20018, severity 15:\nGeneral SQL Server error: Check messages from the SQL Server\n")

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "C:\Users\s\AppData\Local\Continuum\anaconda3\envs\RStudio\lib\site-packages\Orange\canvas\scheme\widgetsscheme.py", line 846, in process_signals_for_widget
    handler(*args)
  File "C:\Users\s\AppData\Local\Continuum\anaconda3\envs\RStudio\lib\site-packages\Orange\widgets\data\owdatasampler.py", line 193, in set_data
    self.commit()
  File "C:\Users\s\AppData\Local\Continuum\anaconda3\envs\RStudio\lib\site-packages\Orange\widgets\data\owdatasampler.py", line 214, in commit
    self.sampleSizeSqlTime, no_cache=True)
  File "C:\Users\s\AppData\Local\Continuum\anaconda3\envs\RStudio\lib\site-packages\Orange\data\sql\table.py", line 596, in sample_time
    no_cache=no_cache)
  File "C:\Users\s\AppData\Local\Continuum\anaconda3\envs\RStudio\lib\site-packages\Orange\data\sql\table.py", line 634, in _sample
    "TABLESAMPLE", method, "(", parameter, ")"])):
  File "C:\Users\s\AppData\Local\Continuum\anaconda3\envs\RStudio\lib\contextlib.py", line 81, in __enter__
    return next(self.gen)
  File "C:\Users\s\AppData\Local\Continuum\anaconda3\envs\RStudio\lib\site-packages\Orange\data\sql\backend\mssql.py", line 79, in execute_sql_query
    raise BackendError(str(ex)) from ex
Orange.data.sql.backend.base.BackendError: (156, b"Incorrect syntax near the keyword 'SELECT'.DB-Lib error message 20018, severity 15:\nGeneral SQL Server error: Check messages from the SQL Server\nDB-Lib error message 20018, severity 15:\nGeneral SQL Server error: Check messages from the SQL Server\n")

Local Variables:
OrderedDict([('cur', <pymssql.Cursor object at 0x00000161D7B88FA8>),
             ('params', ()),
             ('query',
              'CREATE TABLE [dbo].[__Straw1_system_time_1000] AS SELECT * FROM '
              '[dbo].[Straw1] TABLESAMPLE system_time ( 1000 )'),
             ('self',
              <Orange.data.sql.backend.mssql.PymssqlBackend object at 0x00000161CC9C67B8>)])

```
If you're raising an issue about an add-on (e.g. installed via
Options > Add-ons), raise an issue on the relevant add-on's issue
tracker instead. See: https://github.com/biolab?q=orange3
-->

##### Orange version
<!-- From menu _Help→About→Version_ or code `Orange.version.full_version` -->

3.13


##### Expected behavior

Load data from SQL server database

##### Actual behavior

Throws exception whenever a widget is used to access the data:

 b"Incorrect syntax near the keyword 'SELECT'.DB-Lib error message 20018, severity 15:


##### Steps to reproduce the behavior

use the SQL Table widget to connect to SQL database attach and use a widget (select columns, select rows, box plot, data table etc.O



##### Additional info (worksheets, data, screenshots, ...)

![orange sql db error](https://user-images.githubusercontent.com/7368346/40559401-e0537a4e-6013-11e8-9a48-e6750c328edd.jpg)




Discussion:

Resolving patch:
diff --git a/Orange/widgets/data/owsql.py b/Orange/widgets/data/owsql.py
--- a/Orange/widgets/data/owsql.py
+++ b/Orange/widgets/data/owsql.py
@@ -250,6 +250,10 @@ def connect(self):
                 self.download = True
                 self.downloadcb.setEnabled(False)
 
+            if not is_postgres(self.backend):
+                self.download = True
+                self.downloadcb.setEnabled(False)
+
             self._save_credentials()
             self.database_desc = OrderedDict((
                 ("Host", self.host), ("Port", self.port),


Test patch:
diff --git a/Orange/widgets/data/tests/test_owsql.py b/Orange/widgets/data/tests/test_owsql.py
--- a/Orange/widgets/data/tests/test_owsql.py
+++ b/Orange/widgets/data/tests/test_owsql.py
@@ -2,6 +2,7 @@
 # pylint: disable=missing-docstring
 
 import unittest
+from unittest import mock
 
 from Orange.data import Table
 from Orange.widgets.data.owsql import OWSql
@@ -10,7 +11,7 @@
 
 
 @sql_test
-class TestOWSql(WidgetTest):
+class TestOWSqlConnected(WidgetTest):
     def setUp(self):
         self.widget = self.create_widget(OWSql)
         params, _ = create_iris()
@@ -45,18 +46,6 @@ def test_output_iris(self):
         output_domain = set(map(str, output.domain.attributes))
         self.assertTrue(output_domain.issuperset(iris_domain))
 
-    def test_missing_extension(self):
-        """Test for correctly handled missing backend extension"""
-        self.set_connection_params()
-        self.widget.backends[0] = unittest.mock.Mock()
-        self.widget.backends[0]().missing_extension = ["missing extension"]
-        self.widget.backends[0]().list_tables = lambda x: []
-        self.widget.connect()
-
-        self.assertTrue(self.widget.Warning.missing_extension.is_shown())
-        self.assertTrue(self.widget.download)
-        self.assertFalse(self.widget.downloadcb.isEnabled())
-
     def set_connection_params(self):
         """Set database connection parameters on widget"""
         port = ''
@@ -68,5 +57,37 @@ def set_connection_params(self):
         self.widget.passwordtext.setText(self.params['password'])
 
 
+class TestOWSql(WidgetTest):
+
+    @mock.patch('Orange.widgets.data.owsql.Backend')
+    def test_missing_extension(self, mock_backends):
+        """Test for correctly handled missing backend extension"""
+        backend = mock.Mock()
+        backend().display_name = "PostgreSQL"
+        backend().missing_extension = ["missing extension"]
+        backend().list_tables.return_value = []
+        mock_backends.available_backends.return_value = [backend]
+
+        widget = self.create_widget(OWSql)
+
+        self.assertTrue(widget.Warning.missing_extension.is_shown())
+        self.assertTrue(widget.download)
+        self.assertFalse(widget.downloadcb.isEnabled())
+
+    @mock.patch('Orange.widgets.data.owsql.Backend')
+    def test_non_postgres(self, mock_backends):
+        """Test if download is enforced for non postgres backends"""
+        backend = mock.Mock()
+        backend().display_name = "database"
+        del backend().missing_extension
+        backend().list_tables.return_value = []
+        mock_backends.available_backends.return_value = [backend]
+
+        widget = self.create_widget(OWSql)
+
+        self.assertTrue(widget.download)
+        self.assertFalse(widget.downloadcb.isEnabled())
+
+
 if __name__ == "__main__":
     unittest.main()