Repository: mabel-dev/opteryx

Issue:
✨ `IS` filters should be prioritized just after `Eq` filters in the optimizer
### Thanks for stopping by to let us know something could be better!

**Is your feature request related to a problem? Please describe.** _A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]_

**Describe the solution you'd like** _A clear and concise description of what you want to happen._

**Describe alternatives you've considered** _A clear and concise description of any alternative solutions or features you've considered._

**Additional context** _Add any other context or screenshots about the feature request here._



Discussion:

Resolving patch:
diff --git a/opteryx/components/heuristic_optimizer/strategies/split_conjunctive_predicates.py b/opteryx/components/heuristic_optimizer/strategies/split_conjunctive_predicates.py
--- a/opteryx/components/heuristic_optimizer/strategies/split_conjunctive_predicates.py
+++ b/opteryx/components/heuristic_optimizer/strategies/split_conjunctive_predicates.py
@@ -34,6 +34,8 @@ def _tag_predicates(nodes):
         - 35 is something that is expensive (we're running function)
         - 32 is where we're doing a complex comparison
         ...
+        - 7 are IS/IS NOT filters
+        - 5 is doing an eqi comparison on a column and a literal
         - 3 is doing an eqi comparison on two literals (don't actually do that in a filter)
     """
 
@@ -42,6 +44,10 @@ def _tag_predicates(nodes):
         node.simple = True
         node.relations = set()
 
+        if node.condition.node_type == NodeType.UNARY_OPERATOR:
+            # these are IS/IS NOT filters
+            node.weight += 7
+            continue
         if not node.condition.node_type == NodeType.COMPARISON_OPERATOR:
             node.weight += 35
             node.simple = False
diff --git a/opteryx/connectors/sql_connector.py b/opteryx/connectors/sql_connector.py
--- a/opteryx/connectors/sql_connector.py
+++ b/opteryx/connectors/sql_connector.py
@@ -99,20 +99,39 @@ def get_dataset_schema(self) -> RelationSchema:
 
         # get the schema from the dataset
         # DEBUG: log ("GET SQL SCHEMA:", self.dataset)
-        table = Table(self.dataset, self.metadata, autoload_with=self._engine)
-
-        self.schema = RelationSchema(
-            name=table.name,
-            columns=[
-                FlatColumn(
-                    name=column.name,
-                    type=PYTHON_TO_ORSO_MAP[column.type.python_type],
-                    precision=None if column.type.python_type != Decimal else column.type.precision,
-                    scale=None if column.type.python_type != Decimal else column.type.scale,
-                    nullable=column.nullable,
+        try:
+            table = Table(self.dataset, self.metadata, autoload_with=self._engine)
+
+            self.schema = RelationSchema(
+                name=table.name,
+                columns=[
+                    FlatColumn(
+                        name=column.name,
+                        type=PYTHON_TO_ORSO_MAP[column.type.python_type],
+                        precision=None
+                        if column.type.python_type != Decimal
+                        else column.type.precision,
+                        scale=None if column.type.python_type != Decimal else column.type.scale,
+                        nullable=column.nullable,
+                    )
+                    for column in table.columns
+                ],
+            )
+        except Exception as err:
+            # Fall back to getting the schema from the first row, this is the column names, and where
+            # possible, column types.
+            # DEBUG: log ("APPROXIMATING SCHEMA OF {self.dataset} BECAUSE OF {err}")
+            with self._engine.connect() as conn:
+                row = conn.execute(f"SELECT * FROM `{self.dataset}`").fetchone()
+                self.schema = RelationSchema(
+                    name=self.dataset,
+                    columns=[
+                        FlatColumn(
+                            name=column,
+                            type=0 if value is None else PYTHON_TO_ORSO_MAP[type(value)],
+                        )
+                        for column, value in row.items()
+                    ],
                 )
-                for column in table.columns
-            ],
-        )
 
         return self.schema


Test patch:
diff --git a/tests/misc/test_connector_prefixes.py b/tests/misc/test_connector_prefixes.py
--- a/tests/misc/test_connector_prefixes.py
+++ b/tests/misc/test_connector_prefixes.py
@@ -9,7 +9,7 @@
 
 import opteryx
 from opteryx.connectors import GcpFireStoreConnector, SqlConnector, register_store
-from sqlalchemy.exc import NoSuchTableError
+from sqlalchemy.exc import NoSuchTableError, OperationalError
 from opteryx.exceptions import DatasetNotFoundError
 
 register_store(
@@ -48,7 +48,7 @@ def test_connector_prefixes():
 
 
 def test_connector_prefixes_negative_tests():
-    with pytest.raises(NoSuchTableError):
+    with pytest.raises((NoSuchTableError, OperationalError)):
         # this should be the SQLAlchemy error
         opteryx.query("SELECT * from planets.planets")
 
diff --git a/tests/sql_battery/test_shapes_and_errors_battery.py b/tests/sql_battery/test_shapes_and_errors_battery.py
--- a/tests/sql_battery/test_shapes_and_errors_battery.py
+++ b/tests/sql_battery/test_shapes_and_errors_battery.py
@@ -1087,6 +1087,8 @@
         ("SELECT CAST('abc' AS LIST)", None, None, SqlError),
         ("SELECT TRY_CAST('abc' AS LIST)", None, None, SqlError),
 
+        ("SELECT STRUCT(dict) FROM testdata.flat.struct", 3, 1, None),
+
         # V2 Negative Tests
         ("SELECT $planets.id, name FROM $planets INNER JOIN $satellites ON planetId = $planets.id", None, None, AmbiguousIdentifierError),
         ("SELECT $planets.id FROM $satellites", None, None, UnexpectedDatasetReferenceError),