diff --git a/Orange/data/sql/backend/mssql.py b/Orange/data/sql/backend/mssql.py index 2dbeb46d4d1..6d136043472 100644 --- a/Orange/data/sql/backend/mssql.py +++ b/Orange/data/sql/backend/mssql.py @@ -117,17 +117,27 @@ def _guess_variable(self, field_name, field_metadata, inspect_table): EST_ROWS_RE = re.compile(r'StatementEstRows="(\d+)"') def count_approx(self, query): - try: - with self.connection.cursor() as cur: + with self.connection.cursor() as cur: + try: cur.execute("SET SHOWPLAN_XML ON") try: cur.execute(query) result = cur.fetchone() - return int(self.EST_ROWS_RE.search(result[0]).group(1)) + match = self.EST_ROWS_RE.search(result[0]) + if not match: + # Either StatementEstRows was not found or + # a float is received. + # If it is a float then it is most probable + # that the server's statistics are out of date + # and the result is false. In that case + # it is preferable to return None so + # an exact count be used. + return None + return int(match.group(1)) finally: cur.execute("SET SHOWPLAN_XML OFF") - except pymssql.Error as ex: - if "SHOWPLAN permission denied" in str(ex): - warnings.warn("SHOWPLAN permission denied, count approximates will not be used") - return None - raise BackendError(str(ex)) from ex + except pymssql.Error as ex: + if "SHOWPLAN permission denied" in str(ex): + warnings.warn("SHOWPLAN permission denied, count approximates will not be used") + return None + raise BackendError(str(ex)) from ex diff --git a/Orange/widgets/data/owsql.py b/Orange/widgets/data/owsql.py index 585017ff8d6..6c88091d413 100644 --- a/Orange/widgets/data/owsql.py +++ b/Orange/widgets/data/owsql.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import sys from collections import OrderedDict @@ -43,7 +44,7 @@ class OWSql(OWWidget): icon = "icons/SQLTable.svg" priority = 30 category = "Data" - keywords = ["data", "file", "load", "read"] + keywords = ["data", "file", "load", "read", "SQL"] class Outputs: data = Output("Data", Table, doc="Attribute-valued data set read from the input file.") @@ -85,10 +86,10 @@ def __init__(self): vbox = gui.vBox(self.controlArea, "Server", addSpace=True) box = gui.vBox(vbox) - self.backendmodel = BackendModel(Backend.available_backends()) + self.backends = BackendModel(Backend.available_backends()) self.backendcombo = QComboBox(box) - if len(self.backendmodel): - self.backendcombo.setModel(self.backendmodel) + if len(self.backends): + self.backendcombo.setModel(self.backends) else: self.Error.no_backends() box.setEnabled(False) @@ -124,17 +125,24 @@ def __init__(self): box.layout().addWidget(self.passwordtext) self._load_credentials() + self.tables = TableModel() tables = gui.hBox(box) - self.tablemodel = TableModel() self.tablecombo = QComboBox( minimumContentsLength=35, sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLength ) - self.tablecombo.setModel(self.tablemodel) + self.tablecombo.setModel(self.tables) self.tablecombo.setToolTip('table') tables.layout().addWidget(self.tablecombo) + self.connect() + + index = self.tablecombo.findText(str(self.table)) + if index != -1: + self.tablecombo.setCurrentIndex(index) + # set up the callback to select_table in case of selection change self.tablecombo.activated[int].connect(self.select_table) + self.connectbutton = gui.button( tables, self, '↻', callback=self.connect) self.connectbutton.setSizePolicy( @@ -167,7 +175,8 @@ def __init__(self): callback=self.open_table) gui.rubber(self.buttonsArea) - QTimer.singleShot(0, self.connect) + + QTimer.singleShot(0, self.select_table) def _load_credentials(self): self._parse_host_port() @@ -182,8 +191,8 @@ def _load_credentials(self): def _save_credentials(self): cm = self._credential_manager(self.host, self.port) - cm.username = self.username - cm.password = self.password + cm.username = self.username or '' + cm.password = self.password or '' def _credential_manager(self, host, port): return CredentialManager("SQL Table: {}:{}".format(host, port)) @@ -217,7 +226,7 @@ def connect(self): try: if self.backendcombo.currentIndex() < 0: return - backend = self.backendmodel[self.backendcombo.currentIndex()] + backend = self.backends[self.backendcombo.currentIndex()] self.backend = backend(dict( host=self.host, port=self.port, @@ -232,7 +241,6 @@ def connect(self): ("Database", self.database), ("User name", self.username) )) self.refresh_tables() - self.select_table() except BackendError as err: error = str(err).split('\n')[0] self.Error.connection(error) @@ -240,16 +248,17 @@ def connect(self): self.tablecombo.clear() def refresh_tables(self): - self.tablemodel.clear() + self.tables.clear() self.Error.missing_extension.clear() if self.backend is None: self.data_desc_table = None return - self.tablemodel.append("Select a table") - self.tablemodel.extend(self.backend.list_tables(self.schema)) - self.tablemodel.append("Custom SQL") + self.tables.append("Select a table") + self.tables.append("Custom SQL") + self.tables.extend(self.backend.list_tables(self.schema)) + # Called on tablecombo selection change: def select_table(self): curIdx = self.tablecombo.currentIndex() if self.tablecombo.itemText(curIdx) != "Custom SQL": @@ -260,6 +269,8 @@ def select_table(self): self.data_desc_table = None self.database_desc["Table"] = "(None)" self.table = None + if len(str(self.sql)) > 14: + return self.open_table() #self.Error.missing_extension( # 's' if len(missing) > 1 else '', @@ -272,19 +283,22 @@ def open_table(self): self.Outputs.data.send(table) def get_table(self): - if self.tablecombo.currentIndex() <= 0: + curIdx = self.tablecombo.currentIndex() + if curIdx <= 0: if self.database_desc: self.database_desc["Table"] = "(None)" self.data_desc_table = None return - if self.tablecombo.currentIndex() < self.tablecombo.count() - 1: - self.table = self.tablemodel[self.tablecombo.currentIndex()] + if self.tablecombo.itemText(curIdx) != "Custom SQL": + self.table = self.tables[self.tablecombo.currentIndex()] self.database_desc["Table"] = self.table if "Query" in self.database_desc: del self.database_desc["Query"] + what = self.table else: - self.sql = self.table = self.sqltext.toPlainText() + what = self.sql = self.sqltext.toPlainText() + self.table = "Custom SQL" if self.materialize: import psycopg2 if not self.materialize_table_name: @@ -297,11 +311,10 @@ def get_table(self): pass with self.backend.execute_sql_query("CREATE TABLE " + self.materialize_table_name + - " AS " + self.table): + " AS " + self.sql): pass with self.backend.execute_sql_query("ANALYZE " + self.materialize_table_name): pass - self.table = self.materialize_table_name except (psycopg2.ProgrammingError, BackendError) as ex: self.Error.connection(str(ex)) return @@ -312,7 +325,7 @@ def get_table(self): database=self.database, user=self.username, password=self.password), - self.table, + what, backend=type(self.backend), inspect_values=False) except BackendError as ex: @@ -321,8 +334,8 @@ def get_table(self): self.Error.connection.clear() - sample = False + if table.approx_len() > LARGE_TABLE and self.guess_values: confirm = QMessageBox(self) confirm.setIcon(QMessageBox.Warning)