diff --git a/py/api/image_categories.py b/py/api/image_categories.py index 77583c57..9b480de2 100644 --- a/py/api/image_categories.py +++ b/py/api/image_categories.py @@ -139,10 +139,14 @@ class ImageCategoryRoutesMixin: listing = self._favorite_listing(root, files) root_key = _favorite_root_key(root) # normalize_image_path(root / listed) without a call per file: the - # listed spellings are clean relative POSIX paths. + # listed spellings are clean relative POSIX paths. Windows abspath + # also drops a component's trailing dots and spaces, so it keeps + # the call there. base = os.path.join(normalize_image_path(str(root)), "") - def normalized(listed, base=base): + def normalized(listed, base=base, root=root): + if os.name == "nt": + return normalize_image_path(str(root / listed)) return base + os.path.normcase(listed.replace("/", os.sep)) nested = [ diff --git a/tests/test_api_workspace_images.py b/tests/test_api_workspace_images.py index f2db5fdb..0097e7ca 100644 --- a/tests/test_api_workspace_images.py +++ b/tests/test_api_workspace_images.py @@ -1772,6 +1772,17 @@ class TestWorkspaceImages(ImageAPITestCase): "SEARCH other USING COVERING INDEX idx_image_file_path (file_path=?)", plan ) self.assertFalse([step for step in plan if step.startswith("SCAN other")]) + # The category's narrowing clause (#332) keeps every join an index search. + sql, params = link_filter_query( + "", ["tag-1"], every_link=True, category="cat-3" + ) + with self.api.db.model.get_connection() as conn: + plan = [row[3] for row in conn.execute("EXPLAIN QUERY PLAN " + sql, params)] + self.assertIn( + "SEARCH other USING COVERING INDEX idx_image_file_path (file_path=?)", plan + ) + scans = [step for step in plan if step.startswith("SCAN")] + self.assertTrue(all(step.startswith("SCAN gi") for step in scans), plan) @unittest.skipUnless( PERF_BUDGET and not TRACED,