Rajesh Vasa

110 papers A* 2A 6B 13C 5Misc 7Journal 46Unranked 31
YearRankTypeTitle / Venue / Authors
2026 conf
CCNC
Hung Du, Hy Nguyen, Srikanth Thudumu, Rajesh Vasa, Kon Mouzakis
2026 J jnl
Health Inf. Sci. Syst.
Shangeetha Sivasothy, Adrian Bingham, Irini Logothetis, Scott Barnett, Mohamed Abdelrazek, Carl Luckhoff, Joseph Mathew, Rajesh Vasa, Kon Mouzakis
2026 J jnl
Inf. Sci.
Mousa Tayseer Jafar, Lu-Xing Yang, Gang Li, Robin Doss, Kon Mouzakis, Rajesh Vasa, Helge Janicke, Ahmed Ibrahim, Ahmed Mohsin, Iqbal H. Sarker, Kristen Moore, Seyit Camtepe, Diksha Goel
2025 A conf
ECAI
Hy Nguyen, Bao Pham, Srikanth Thudumu, Hung Du, Rajesh Vasa, Kon Mouzakis
2025 J jnl
CoRR
Hy Nguyen, Bao Pham, Hung Du, Srikanth Thudumu, Rajesh Vasa, Kon Mouzakis
2025 J jnl
CoRR
Hung Du, Srikanth Thudumu, Hy Nguyen, Rajesh Vasa, Kon Mouzakis
2025 J jnl
CoRR
Hy Nguyen, Nguyen Hung Nguyen, Nguyen Linh Bao Nguyen, Srikanth Thudumu, Hung Du, Rajesh Vasa, Kon Mouzakis
2025 J jnl
CoRR
Hala Abdelkader, Mohamed Abdelrazek, Priya Rani, Rajesh Vasa, Jean-Guy Schneider
2025 J jnl
CoRR
Hung Du, Hy Nguyen, Srikanth Thudumu, Rajesh Vasa, Kon Mouzakis
2025 conf
AMCIS
Hy Nguyen, Nguyen Hung Nguyen, Linh Bao Nguyen, Srikanth Thudumu, Rajesh Vasa
2025 J jnl
CoRR
Hy Nguyen, Duy Khoa Pham, Srikanth Thudumu, Hung Du, Rajesh Vasa, Kon Mouzakis
2025 conf
eScience
Hy Nguyen, Srikanth Thudumu, Hung Du, Rajesh Vasa, Kon Mouzakis
2025 conf
AMCIS
Hy Nguyen, Srikanth Thudumu, Hung Du, Rajesh Vasa, Kon Mouzakis
2025 J jnl
CoRR
Hy Nguyen, Srikanth Thudumu, Hung Du, Rajesh Vasa, Kon Mouzakis
2025 B conf
CAIN
Shangeetha Sivasothy, Scott Barnett, Stefanus Kurniawan, Zafaryab Rasool, Rajesh Vasa
2025 B conf
CAIN
Hala Abdelkader, Mohamed Almorsy Abdelrazek, Sankhya Singh, Irini Logothetis, Priya Rani, Rajesh Vasa, Jean-Guy Schneider
2025 J jnl
CoRR
Srikanth Thudumu, Hy Nguyen, Hung Du, Nhat Duong, Zafaryab Rasool, Rena Logothetis, Scott Barnett, Rajesh Vasa, Kon Mouzakis
2024 J jnl
CoRR
Hung Du, Srikanth Thudumu, Rajesh Vasa, Kon Mouzakis
2024 J jnl
Empir. Softw. Eng.
Tuan Dung Lai, Anj Simmons, Scott Barnett, Jean-Guy Schneider, Rajesh Vasa
2024 J jnl
Nat. Lang. Process. J.
Zafaryab Rasool, Stefanus Kurniawan, Sherwin Balugo, Scott Barnett, Rajesh Vasa, Courtney Chesser, Benjamin M. Hampstead, Sylvie Belleville, Kon Mouzakis, Alex Bahar-Fuchs
2024 B conf
CAIN
Hala Abdelkader, Mohamed Abdelrazek, Scott Barnett, Jean-Guy Schneider, Priya Rani, Rajesh Vasa
2024 J jnl
CoRR
Hala Abdelkader, Mohamed Abdelrazek, Scott Barnett, Jean-Guy Schneider, Priya Rani, Rajesh Vasa
2024 J jnl
CoRR
Shangeetha Sivasothy, Scott Barnett, Stefanus Kurniawan, Zafaryab Rasool, Rajesh Vasa
2024 A* conf
ASE
Hala Abdelkader, Jean-Guy Schneider, Mohamed Abdelrazek, Priya Rani, Rajesh Vasa
2023 conf
AI (1)
Venkat Munagala, Sankhya Singh, Srikanth Thudumu, Irini Logothetis, Sushil Bhandari, Amit Bhandari, Kon Mouzakis, Rajesh Vasa
2023 J jnl
J. Big Data
Hung Du, Srikanth Thudumu, Antonio Giardina, Rajesh Vasa, Kon Mouzakis, Li Jiang, John Chisholm, Sanat Bista
2023 conf
CCNC
Hung Du, Srikanth Thudumu, Sankhya Singh, Scott Barnett, Irini Logothetis, Rajesh Vasa, Kon Mouzakis
2023 J jnl
CoRR
Zafaryab Rasool, Scott Barnett, Stefanus Kurniawan, Sherwin Balugo, Rajesh Vasa, Courtney Chesser, Alex Bahar-Fuchs
2023 conf
GLOBECOM (Workshops)
Aleksandar Pasquini, Rajesh Vasa, Hassan Habibi Gharakheili, Irini Logothetis, Minh Tran, Alexander Chambers
2023 J jnl
CoRR
Anj Simmons, Rajesh Vasa
2023 conf
SE4SafeML@SIGSOFT FSE
Sheng Wong, Scott Barnett, Jessica Rivera-Villicana, Anj Simmons, Hala Abdelkader, Jean-Guy Schneider, Rajesh Vasa
2023 J jnl
CoRR
Sheng Wong, Scott Barnett, Jessica Rivera-Villicana, Anj Simmons, Hala Abdelkader, Jean-Guy Schneider, Rajesh Vasa
2023 J jnl
CoRR
Irini Logothetis, Priya Rani, Shangeetha Sivasothy, Rajesh Vasa, Kon Mouzakis
2023 J jnl
Algorithms
Hy Nguyen, Srikanth Thudumu, Hung Du, Kon Mouzakis, Rajesh Vasa
2022 B conf
e-Science
Hung Du, Srikanth Thudumu, Sankhya Singh, Scott Barnett, Irini Logothetis, Rajesh Vasa, Kon Mouzakis
2022 J jnl
CoRR
Tuan Dung Lai, Anj Simmons, Scott Barnett, Jean-Guy Schneider, Rajesh Vasa
2022 B conf
e-Science
Irini Logothetis, Scott Barnett, Leonard Hoon, Srikanth Thudumu, Joseph Mathew, Carl Luckhoff, Gerard O'Reilly, David Collard, Rajesh Vasa, Kon Mouzakis, Mark Fitzgerald
2022 J jnl
IEEE Trans. Software Eng.
Alex Cummaudo, Rajesh Vasa, John C. Grundy, Mohamed Abdelrazek
2022 J jnl
CoRR
Anj Simmons, Rajesh Vasa
2022 conf
ISWC (Posters/Demos/Industry)
Anj Simmons, Rajesh Vasa, Antonio Giardina
2022 J jnl
CoRR
Anj Simmons, Rajesh Vasa, Antonio Giardina
2022 B conf
e-Science
Johnahan Van Zyl, Hung Du, Srikanth Thudumu, Irini Logothetis, Scott Barnett, Rajesh Vasa, Kon Mouzakis
2021 conf
SEmotion@ICSE
Alex Cummaudo, Ulrike Maria Graetsch, Maheswaree Kissoon Curumsing, Rajesh Vasa, Scott Barnett, John Grundy
2021 C conf
SCAM
Shangeetha Sivasothy, Scott Barnett, Niroshinie Fernando, Rajesh Vasa, Roopak Sinha, Andrew J. Simmons
2021 C conf
ICIS
Roxanne Baudilla Llamzon, Felix T. C. Tan, Lemuria D. Carter, Kon Mouzakis, Rajesh Vasa
2020 A conf
ESEM
Andrew J. Simmons, Scott Barnett, Jessica Rivera-Villicana, Akshat Bajaj, Rajesh Vasa
2020 J jnl
CoRR
Andrew J. Simmons, Scott Barnett, Jessica Rivera-Villicana, Akshat Bajaj, Rajesh Vasa
2020 J jnl
Inf. Softw. Technol.
Nor Shahida Mohamad Yusop, John Grundy, Jean-Guy Schneider, Rajesh Vasa
2020 J jnl
CoRR
Alex Cummaudo, Scott Barnett, Rajesh Vasa, John C. Grundy, Mohamed Abdelrazek
2020 conf
ESEC/SIGSOFT FSE
Alex Cummaudo, Scott Barnett, Rajesh Vasa, John C. Grundy, Mohamed Abdelrazek
2020 J jnl
CoRR
Alex Cummaudo, Rajesh Vasa, Scott Barnett, John C. Grundy, Mohamed Abdelrazek
2020 A* conf
ICSE
Alex Cummaudo, Rajesh Vasa, Scott Barnett, John C. Grundy, Mohamed Abdelrazek
2020 J jnl
CoRR
Maheswaree Kissoon Curumsing, Alex Cummaudo, Ulrike Maria Graetsch, Scott Barnett, Rajesh Vasa
2020 J jnl
CoRR
Alex Cummaudo, Rajesh Vasa, John C. Grundy, Mohamed Almorsy Abdelrazek
2020 J jnl
CoRR
Alex Cummaudo, Scott Barnett, Rajesh Vasa, John C. Grundy
2020 conf
ESEC/SIGSOFT FSE
Alex Cummaudo, Scott Barnett, Rajesh Vasa, John C. Grundy
2019 J jnl
J. Syst. Softw.
Maheswaree Kissoon Curumsing, Niroshinie Fernando, Mohamed Abdelrazek, Rajesh Vasa, Kon Mouzakis, John Grundy
2019 A conf
ICSME
Alex Cummaudo, Rajesh Vasa, John C. Grundy, Mohamed Abdelrazek, Andrew Cain
2019 J jnl
CoRR
Alex Cummaudo, Rajesh Vasa, John C. Grundy, Mohamed Abdelrazek, Andrew Cain
2019 B conf
ICWE
Tomohiro Ohtake, Alex Cummaudo, Mohamed Abdelrazek, Rajesh Vasa, John C. Grundy
2019 J jnl
J. Comput. Lang.
Scott Barnett, Iman Avazpour, Rajesh Vasa, John Grundy
2019 A conf
ESEM
Alex Cummaudo, Rajesh Vasa, John Grundy
2019 J jnl
CoRR
Alex Cummaudo, Rajesh Vasa, John C. Grundy
2018 Misc conf
OZCHI
Andrew J. Simmons, Maheswaree Kissoon Curumsing, Rajesh Vasa
2018 J jnl
CoRR
Andrew J. Simmons, Scott Barnett, Simon Vajda, Rajesh Vasa
2018 conf
ASWEC
Law Check Yee, John C. Grundy, Karola von Baggo, Andrew Cain, Rajesh Vasa
2018 J jnl
Appl. Soft Comput.
Gleb Beliakov, Marek Gagolewski, Simon James, Shannon Pace, Nicola Pastorello, Elodie Thilliez, Rajesh Vasa
2018 conf
ASWEC
Nor Shahida Mohamad Yusop, John C. Grundy, Jean-Guy Schneider, Rajesh Vasa
2017 C conf
APSEC
Nor Shahida Mohamad Yusop, Jean-Guy Schneider, John Grundy, Rajesh Vasa
2017 J jnl
IEEE Trans. Software Eng.
Nor Shahida Mohamad Yusop, John Grundy, Rajesh Vasa
2017 conf
SIGSPATIAL/GIS
Andrew J. Simmons, Rajesh Vasa
2017 J jnl
CoRR
Andrew J. Simmons, Rajesh Vasa
2017 conf
XP Workshops
Antonio Martini, Simon Vajda, Rajesh Vasa, Allan Jones, Mohamed Abdelrazek, John Grundy, Jan Bosch
2017 C conf
ACE
Check Yee Law, John C. Grundy, Andrew Cain, Rajesh Vasa, Alex Cummaudo
2016 B conf
VL/HCC
Check Yee Law, John Grundy, Rajesh Vasa, Andrew Cain
2016 conf
ECIS
Niroshinie Fernando, Felix Ter Chian Tan, Rajesh Vasa, Kon Mouzakis, Ian Aitken
2016 A conf
EASE
Nor Shahida Mohamad Yusop, John C. Grundy, Rajesh Vasa
2016 Misc conf
OZCHI
Leonard Hoon, Milica Stojmenovic, Rajesh Vasa, Graham Farrell
2016 C conf
APSEC
Nor Shahida Mohamad Yusop, Jean-Guy Schneider, John C. Grundy, Rajesh Vasa
2015 conf
WICSA
Scott Barnett, Rajesh Vasa, Antony Tang
2015 B conf
COMPSAC
Check Yee Law, John C. Grundy, Andrew Cain, Rajesh Vasa
2015 B conf
VL/HCC
Scott Barnett, Iman Avazpour, Rajesh Vasa, John C. Grundy
2015 conf
ICSE (2)
Scott Barnett, Rajesh Vasa, John Grundy
2015 B conf
VL/HCC
Andrew J. Simmons, Iman Avazpour, Hai Le Vu, Rajesh Vasa
2015 J jnl
Australas. J. Inf. Syst.
Leonard Hoon, Felix Ter Chian Tan, Rajesh Vasa, Kon Mouzakis, Mark Fitzgerald
2015 B conf
e-Science
Antonio Giardina, Yun Yang, Hai Le Vu, Rajesh Vasa
2015 B conf
CLOUD
Qiang He, Jun Han, Feifei Chen, Yanchun Wang, Rajesh Vasa, Yun Yang, Hai Jin
2015 conf
ASWEC (2)
Nor Shahida Mohamad Yusop, John Grundy, Rajesh Vasa
2015 conf
SCC
Qiang He, Xiaoyuan Xie, Feifei Chen, Yanchun Wang, Rajesh Vasa, Yun Yang, Hai Jin
2015 J jnl
Int. J. People Oriented Program.
Maheswaree Kissoon Curumsing, Antonio A. Lopez-Lorca, Tim Miller, Leon Sterling, Rajesh Vasa
2014 J jnl
Int. J. People Oriented Program.
Maheswaree Kissoon Curumsing, Sonja Pedell, Rajesh Vasa
2013 Misc conf
OZCHI
Leonard Hoon, Rajesh Vasa, Gloria Yoanita Martino, Jean-Guy Schneider, Kon Mouzakis
2012 Misc conf
OZCHI
Rajesh Vasa, Leonard Hoon, Kon Mouzakis, Akihiro Noguchi
2012 Misc conf
OZCHI
Leonard Hoon, Rajesh Vasa, Jean-Guy Schneider, Kon Mouzakis
2012 Misc conf
OZCHI
Antonio Giardina, Rajesh Vasa, Felix Ter Chian Tan
2012 J jnl
Int. J. Softw. Eng. Knowl. Eng.
Markus Lumpe, Rajesh Vasa, Tim Menzies, Rebecca Rush, Burak Turhan
2012 Misc ed.
OZCHI
Vivienne Farrell, Graham Farrell, Caslon Chua, Weidong Huang, Rajesh Vasa, Clinton Woodward
2011 conf
ECIS
Felix Ter Chian Tan, Rosemary Stockdale, Rajesh Vasa
2011 conf
ACIS
Felix Ter Chian Tan, Rajesh Vasa
2010 conf
EVOL/IWPSE
Jean-Guy Schneider, Rajesh Vasa, Leonard Hoon
2010 conf
Australian Software Engineering Conference
Markus Lumpe, Samiran Mahmud, Rajesh Vasa
2010 conf
WCSI
Markus Lumpe, Rajesh Vasa
2009 conf
ICSM
Rajesh Vasa, Markus Lumpe, Philip Branch, Oscar Nierstrasz
2009 J jnl
IEEE Softw.
Antony Tang, Jun Han, Rajesh Vasa
2008 J jnl
Electron. Commun. Eur. Assoc. Softw. Sci. Technol.
Rajesh Vasa, Jean-Guy Schneider, Oscar Nierstrasz, Clinton J. Woodward
2007 A conf
SC
Rajesh Vasa, Markus Lumpe, Jean-Guy Schneider
2007 conf
ICSM
Rajesh Vasa, Jean-Guy Schneider, Oscar Nierstrasz
2006 conf
ASWEC
Jean-Guy Schneider, Rajesh Vasa
2005 conf
ISESE
Rajesh Vasa, Jean-Guy Schneider, Clinton J. Woodward, Andrew Cain
2003 conf
ECOOP Workshops
Serge Demeyer, Stéphane Ducasse, Kim Mens, Adrian Trifu, Rajesh Vasa, Filip Van Rysselberghe
redb/queries.py
← Index redb/queries.py python
"""
Database query functions for REDB.

Contains all functions that query ClickHouse for sample metadata,
deduplication checks, and catalog lookups.
"""
import os
import ast
import clickhouse_connect
from typing import List, Optional, Dict

from redb import settings
from redb.s3_utils import generate_s3_key_from_hash


def get_supported_formats(magika_filter: Optional[str] = None) -> List[str]:
    """
    Get supported file formats from SUPPORTED_FORMATS env variable or magika_filter override.
    Expected format: SUPPORTED_FORMATS=['pebin', 'elf']

    Args:
        magika_filter: Optional single format to filter by (overrides env var)

    Returns:
        List of supported format strings, defaults to ['pebin'] if not set
    """
    # If magika_filter is provided, use it as the only format
    if magika_filter:
        return [magika_filter]

    formats_str = os.getenv('SUPPORTED_FORMATS', "['pebin']")
    try:
        formats = ast.literal_eval(formats_str)
        if isinstance(formats, list) and all(isinstance(f, str) for f in formats):
            return formats
        else:
            print(f"[WARNING] SUPPORTED_FORMATS must be a list of strings, got: {formats_str}")
            return ['pebin']
    except (ValueError, SyntaxError) as e:
        print(f"[WARNING] Failed to parse SUPPORTED_FORMATS '{formats_str}': {e}")
        return ['pebin']


def get_db_catalog_connection():
    """Create and return a ClickHouse client for catalog queries"""
    return clickhouse_connect.get_client(
        host=os.getenv("DB_HOST"),
        port=int(os.getenv("DB_PORT", "8123")),
        verify=os.getenv("DB_ENFORCE_SSL", "False").lower() == "true",
        username=os.getenv("DB_USER"),
        password=os.getenv("DB_PASSWORD"),
        database=os.getenv("DB_NAME")
    )


def fetch_s3_objects_by_repository(repository: Optional[str], index_prefix: str, decompile: bool, notes: Optional[str] = None, magika_filter: Optional[str] = None, yara_scan: bool = False, force: bool = False) -> List[Dict]:
    """
    Query the ClickHouse repository_upload_sessions table to get samples.
    S3 bucket comes from env var, S3 key is derived from sha256.

    Args:
        repository: Repository name (e.g., "bazaar", "malshare"). If None or "all-repos", queries all repositories.
        index_prefix: Table prefix for checking existing samples
        decompile: Whether we're in decompile mode (affects which table to check for existing)
        notes: Optional filter for notes field
        magika_filter: Optional single filetype to filter by (overrides SUPPORTED_FORMATS)
        yara_scan: Whether we're in YARA-only mode (skips "already processed" check)

    Returns:
        List of dicts with sha256, s3_bucket, s3_key for each sample
    """
    try:
        client = get_db_catalog_connection()
        s3_bucket = os.getenv('S3_BUCKET')

        if not s3_bucket:
            print("[ERROR] S3_BUCKET environment variable is required")
            return []

        # Get supported formats from env or magika_filter override
        supported_formats = get_supported_formats(magika_filter)
        print(f"[INFO] Querying for supported formats: {supported_formats}")

        # Build query - repository filter is optional
        # Join with catalog_samples to get first_seen date
        if repository and repository != "all-repos":
            query = """
                SELECT DISTINCT rus.sha256, rus.filetype_magika, cs.first_seen
                FROM repository_upload_sessions rus
                LEFT JOIN catalog_samples cs ON rus.sha256 = cs.sha256
                WHERE rus.repository = %(repo)s
                  AND rus.filetype_magika IN %(formats)s
            """
            params = {"repo": repository, "formats": supported_formats}
        else:
            # Query all repositories
            query = """
                SELECT DISTINCT rus.sha256, rus.filetype_magika, cs.first_seen
                FROM repository_upload_sessions rus
                LEFT JOIN catalog_samples cs ON rus.sha256 = cs.sha256
                WHERE rus.filetype_magika IN %(formats)s
            """
            params = {"formats": supported_formats}

        if notes:
            query += " AND rus.notes LIKE %(notes)s"
            params["notes"] = f"%{notes}%"

        # Execute query and convert to list of dictionaries
        result = client.query(query, parameters=params)

        # Build rows with s3_bucket and s3_key derived from sha256
        rows = []
        for row in result.result_rows:
            sha256 = row[0]
            # Handle binary string if needed
            if isinstance(sha256, bytes):
                sha256 = sha256.decode('utf-8')

            rows.append({
                'sha256': sha256,
                's3_bucket': s3_bucket,
                's3_key': generate_s3_key_from_hash(sha256),
                'filetype_magika': row[1],
                'first_seen': row[2]  # From catalog_samples (None if not found)
            })

        print(f"[DEBUG] Found {len(rows)} objects in repository_upload_sessions for repository {repository}")

        # Extract SHA256 hashes from the results for bulk checking
        sha256_list = [row.get('sha256') for row in rows if row.get('sha256')]

        if sha256_list and not force:
            # Check which hashes are already in the database
            existing_hashes = is_in_db_bulk(sha256_list, index_prefix, decompile, yara_scan, magika_filter)
            print(f"[INFO] Found {len(existing_hashes)} objects already in database")

            # Filter out rows with existing hashes
            rows = [row for row in rows if row.get('sha256') not in existing_hashes]
            print(f"[INFO] After filtering, {len(rows)} objects remain to be processed")
        elif force:
            print(f"[INFO] Force mode: skipping deduplication check, processing all {len(sha256_list)} objects")

        # Randomize the order of rows before returning
        import random
        random.shuffle(rows)

        return rows
    except Exception as e:
        print(f"[ERROR] Failed to query repository_upload_sessions: {e}")
        return []
    finally:
        if 'client' in locals():
            client.close()


def fetch_s3_objects_by_date_range(
    index_prefix: str,
    decompile: bool,
    start_date: str,
    end_date: str,
    repository: Optional[str] = None,
    notes: Optional[str] = None,
    magika_filter: Optional[str] = None,
    yara_scan: bool = False,
    force: bool = False,
    analyzed: bool = False
) -> List[Dict]:
    """
    Query samples from catalog_samples by first_seen date range, filtered to only include
    samples that exist in repository_upload_sessions (bulk/repo uploads only).

    Args:
        index_prefix: Table prefix for checking existing samples
        decompile: Whether we're in decompile mode
        start_date: Start date (inclusive) in YYYY-MM-DD format
        end_date: End date (exclusive) in YYYY-MM-DD format
        repository: Optional repository filter
        notes: Optional notes filter
        magika_filter: Optional single filetype to filter by (overrides SUPPORTED_FORMATS)
        yara_scan: Whether we're in YARA-only mode (skips "already processed" check)
        analyzed: Filter to only samples already in basic_properties (cross-database join)

    Returns:
        List of dicts with sha256, s3_bucket, s3_key for each sample
    """
    try:
        client = get_db_catalog_connection()
        s3_bucket = os.getenv('S3_BUCKET')

        if not s3_bucket:
            print("[ERROR] S3_BUCKET environment variable is required")
            return []

        # Get supported formats from env or magika_filter override
        supported_formats = get_supported_formats(magika_filter)
        print(f"[INFO] Querying for supported formats: {supported_formats}")

        # Join catalog_samples with repository_upload_sessions to:
        # 1. Filter by first_seen date from catalog_samples
        # 2. Only include samples that exist in repository_upload_sessions (not user uploads)
        # 3. Optionally filter to only already-analyzed samples (in basic_properties)
        analyzed_join = ""
        if analyzed:
            basic_table = f"{index_prefix}_basic_properties"
            analyzed_join = f"INNER JOIN {basic_table} bp ON cs.sha256 = bp.sha256"
            print(f"[INFO] Filtering to already-analyzed samples in {basic_table}")

        query = f"""
            SELECT DISTINCT cs.sha256, rus.filetype_magika, cs.first_seen
            FROM catalog_samples cs
            INNER JOIN repository_upload_sessions rus ON cs.sha256 = rus.sha256
            {analyzed_join}
            WHERE cs.first_seen >= %(start_date)s
              AND cs.first_seen < %(end_date)s
              AND rus.filetype_magika IN %(formats)s
        """
        params = {
            "start_date": start_date,
            "end_date": end_date,
            "formats": supported_formats
        }

        if repository:
            query += " AND rus.repository = %(repo)s"
            params["repo"] = repository

        if notes:
            query += " AND rus.notes LIKE %(notes)s"
            params["notes"] = f"%{notes}%"

        # Execute query
        result = client.query(query, parameters=params)

        # Build rows with s3_bucket and s3_key derived from sha256
        rows = []
        for row in result.result_rows:
            sha256 = row[0]
            # Handle binary string if needed
            if isinstance(sha256, bytes):
                sha256 = sha256.decode('utf-8')

            rows.append({
                'sha256': sha256,
                's3_bucket': s3_bucket,
                's3_key': generate_s3_key_from_hash(sha256),
                'filetype_magika': row[1],
                'first_seen': row[2]  # From catalog_samples
            })

        date_info = f"from {start_date} to {end_date}"
        repo_info = f" for repository {repository}" if repository else ""
        print(f"[DEBUG] Found {len(rows)} objects in catalog_samples {date_info}{repo_info}")

        # Extract SHA256 hashes from the results for bulk checking
        sha256_list = [row.get('sha256') for row in rows if row.get('sha256')]

        if sha256_list and not force:
            # Check which hashes are already in the database
            existing_hashes = is_in_db_bulk(sha256_list, index_prefix, decompile, yara_scan, magika_filter)
            print(f"[INFO] Found {len(existing_hashes)} objects already in database")

            # Filter out rows with existing hashes
            rows = [row for row in rows if row.get('sha256') not in existing_hashes]
            print(f"[INFO] After filtering, {len(rows)} objects remain to be processed")
        elif force:
            print(f"[INFO] Force mode: skipping deduplication check, processing all {len(sha256_list)} objects")

        # Randomize the order of rows before returning
        import random
        random.shuffle(rows)

        return rows
    except Exception as e:
        print(f"[ERROR] Failed to query catalog_samples by date range: {e}")
        return []
    finally:
        if 'client' in locals():
            client.close()


def fetch_analyzed_samples(
    index_prefix: str,
    decompile: bool,
    magika_filter: Optional[str] = None,
    yara_scan: bool = False,
    force: bool = False,
    rerun: bool = False
) -> List[Dict]:
    """
    Query samples from basic_properties that have already been analyzed.
    Useful for reprocessing with decompilation or specific modules.

    Args:
        index_prefix: Table prefix for ClickHouse
        decompile: Whether we're in decompile mode (affects filtering)
        magika_filter: Optional single filetype to filter by (overrides SUPPORTED_FORMATS)
        yara_scan: Whether we're in YARA-only mode (skips decompile filtering)
        force: Skip all deduplication checks when True
        rerun: Query disassembled table directly (only already-disassembled samples)

    Returns:
        List of dicts with sha256, s3_bucket, s3_key for each analyzed sample
    """
    try:
        client = settings.create_clickhouse_client()
        s3_bucket = os.getenv('S3_BUCKET')

        if not s3_bucket:
            print("[ERROR] S3_BUCKET environment variable is required")
            return []

        # Rerun mode: query directly from disassembled_functions_references
        # instead of basic_properties. This targets only samples that already
        # went through binja successfully.
        if rerun:
            disassembled_table = _get_code_dedup_table(magika_filter)
            basic_table = f"{index_prefix}_basic_properties"

            # Join with basic_properties to get filetype_magika and apply format filters
            conditions = []
            params = {}

            if magika_filter:
                conditions.append("bp.filetype_magika = %(magika)s")
                params["magika"] = magika_filter
            else:
                supported_formats = get_supported_formats()
                conditions.append("bp.filetype_magika IN %(formats)s")
                params["formats"] = supported_formats

            query = (
                f"SELECT DISTINCT d.sha256, bp.filetype_magika "
                f"FROM {disassembled_table} d FINAL "
                f"INNER JOIN {basic_table} bp FINAL ON d.sha256 = bp.sha256"
            )
            if conditions:
                query += " WHERE " + " AND ".join(conditions)

            result = client.query(query, parameters=params)

            rows = []
            for row in result.result_rows:
                sha256 = row[0]
                if isinstance(sha256, bytes):
                    sha256 = sha256.decode('utf-8')
                rows.append({
                    'sha256': sha256,
                    's3_bucket': s3_bucket,
                    's3_key': generate_s3_key_from_hash(sha256),
                    'filetype_magika': row[1]
                })

            print(f"[INFO] Rerun mode: found {len(rows)} already-disassembled samples in {disassembled_table}")

            import random
            random.shuffle(rows)
            return rows

        basic_table = f"{index_prefix}_basic_properties"

        # Build query
        conditions = []
        params = {}

        if magika_filter:
            conditions.append("filetype_magika = %(magika)s")
            params["magika"] = magika_filter
        else:
            supported_formats = get_supported_formats()
            conditions.append("filetype_magika IN %(formats)s")
            params["formats"] = supported_formats

        query = f"SELECT DISTINCT sha256, filetype_magika FROM {basic_table} FINAL"
        if conditions:
            query += " WHERE " + " AND ".join(conditions)

        result = client.query(query, parameters=params)

        rows = []
        for row in result.result_rows:
            sha256 = row[0]
            if isinstance(sha256, bytes):
                sha256 = sha256.decode('utf-8')
            rows.append({
                'sha256': sha256,
                's3_bucket': s3_bucket,
                's3_key': generate_s3_key_from_hash(sha256),
                'filetype_magika': row[1]
            })

        print(f"[DEBUG] Found {len(rows)} analyzed samples in {basic_table}")

        # In YARA-only mode, filter out already-scanned samples (unless force)
        if yara_scan and not force:
            sha256_list = [r['sha256'] for r in rows]
            if sha256_list:
                already_scanned = _check_yara_matches_bulk(sha256_list)
                print(f"[INFO] Found {len(already_scanned)} already YARA-scanned samples")
                rows = [r for r in rows if r['sha256'].lower() not in already_scanned]
                print(f"[INFO] After filtering, {len(rows)} samples remain for YARA scanning")
        # In decompile mode, filter out already-disassembled samples (unless force)
        # We check disassembly (not decompilation) because disassembly is the ground truth:
        # disassembly always succeeds, decompilation may not, so a missing decompile
        # entry doesn't mean the sample wasn't analyzed.
        elif decompile and not force:
            sha256_list = [r['sha256'] for r in rows]
            if sha256_list:
                disassembled_table = _get_code_dedup_table(magika_filter)
                batch_size = 3900

                already_disassembled = set()
                for i in range(0, len(sha256_list), batch_size):
                    batch = sha256_list[i:i+batch_size]
                    placeholders = "','".join(batch)
                    check_query = f"SELECT DISTINCT sha256 FROM {disassembled_table} FINAL WHERE sha256 IN ('{placeholders}')"
                    check_result = client.query(check_query)
                    for check_row in check_result.result_rows:
                        hash_value = check_row[0]
                        if isinstance(hash_value, bytes):
                            hash_value = hash_value.decode('utf-8')
                        already_disassembled.add(hash_value)

                print(f"[INFO] Found {len(already_disassembled)} already-disassembled samples")
                rows = [r for r in rows if r['sha256'] not in already_disassembled]
                print(f"[INFO] After filtering, {len(rows)} samples remain for decompilation")
        elif force:
            print(f"[INFO] Force mode: returning all {len(rows)} analyzed samples")

        # Randomize the order of rows before returning
        import random
        random.shuffle(rows)

        return rows
    except Exception as e:
        print(f"[ERROR] Failed to query analyzed samples: {e}")
        return []
    finally:
        if 'client' in locals():
            client.close()


def is_in_db(sha256, index_prefix, client=None):
    """
    Check if a sample with given SHA256 already exists in the database

    Args:
        sha256: File's SHA256 hash
        index_prefix: Table prefix for ClickHouse
        client: Optional ClickHouse client instance

    Returns:
        bool: True if file exists, False otherwise
    """
    table = f"{index_prefix}_basic_properties"
    close_client = False

    try:
        if client is None:
            client = settings.create_clickhouse_client()
            close_client = True

        # No need for FINAL when checking existence with LIMIT 1
        # Any version of the row proves the sample exists
        query = f"SELECT 1 FROM {table} WHERE sha256 = %(sha256)s LIMIT 1"
        result = client.query(query, parameters={"sha256": sha256})

        return len(result.result_rows) > 0
    except Exception as e:
        print(f"[ERROR] Failed to check if file is in DB: {e}")
        return False
    finally:
        if close_client and client:
            client.close()


def _get_code_dedup_table(magika_filter: Optional[str] = None) -> str:
    """
    Return the code-analysis table used for deduplication based on filetype.

    APK samples are disassembled into code_apk_smali_methods_references;
    everything else uses {CLICKHOUSE_CODE_PREFIX}_disassembled_functions_references.
    """
    if magika_filter == "apk":
        return "code_apk_smali_methods_references"
    code_prefix = os.getenv("CLICKHOUSE_CODE_PREFIX", "code_binja")
    return f"{code_prefix}_disassembled_functions_references"


def is_in_code_db(sha256, client=None, filetype=None):
    """
    Check if a sample with given SHA256 has already been disassembled.

    Args:
        sha256: File's SHA256 hash
        client: Optional ClickHouse client instance
        filetype: Magika filetype label (e.g. 'apk') to pick the right code table

    Returns:
        bool: True if file has been disassembled, False otherwise
    """
    table = _get_code_dedup_table(filetype)
    close_client = False

    try:
        if client is None:
            client = settings.create_clickhouse_client()
            close_client = True

        query = f"SELECT 1 FROM {table} WHERE sha256 = %(sha256)s LIMIT 1"
        result = client.query(query, parameters={"sha256": sha256})

        return len(result.result_rows) > 0
    except Exception as e:
        print(f"[ERROR] Failed to check if file is in code DB: {e}")
        return False
    finally:
        if close_client and client:
            client.close()


def _check_yara_matches_bulk(sha256_list):
    """
    Check which samples have already been YARA-scanned by querying yara_matches.

    The yara_matches table uses FixedString(32) binary sha256, so we convert
    hex strings with unhex().

    Args:
        sha256_list: List of hex SHA256 strings to check

    Returns:
        set: Set of hex SHA256 hashes that already have YARA matches
    """
    # unhex('64hexchars') adds 9 chars overhead per entry vs plain '64hexchars'.
    # 3900 works for plain strings (~67 chars each = 261K) but overflows
    # max_query_size (262144) with unhex() wrapping (~73 chars each = 285K).
    # 3500 × 73 = 255K stays safely under the limit.
    batch_size = 3500
    already_scanned = set()

    try:
        client = settings.create_clickhouse_client()

        total_batches = (len(sha256_list) + batch_size - 1) // batch_size
        for batch_num, i in enumerate(range(0, len(sha256_list), batch_size), 1):
            batch = sha256_list[i:i+batch_size]
            unhex_list = ",".join(f"unhex('{h}')" for h in batch)
            query = f"SELECT DISTINCT hex(sha256) FROM yara_matches FINAL WHERE sha256 IN ({unhex_list})"
            result = client.query(query)
            for row in result.result_rows:
                hash_value = row[0]
                if isinstance(hash_value, bytes):
                    hash_value = hash_value.decode('utf-8')
                already_scanned.add(hash_value.lower())

            if batch_num % 100 == 0 or batch_num == total_batches:
                print(f"[INFO] YARA dedup progress: batch {batch_num}/{total_batches}, found {len(already_scanned)} so far")

        print(f"[INFO] YARA dedup: found {len(already_scanned)} already-scanned samples")
        return already_scanned

    except Exception as e:
        raise RuntimeError(f"YARA dedup query failed — aborting to prevent reprocessing all samples: {e}")
    finally:
        if 'client' in locals():
            client.close()


def is_in_db_bulk(sha256_list, index_prefix, decompile, yara_scan=False, magika_filter=None):
    """
    Check which samples from a list of SHA256 hashes should be skipped.

    Args:
        sha256_list: List of SHA256 hashes to check
        index_prefix: Table prefix for ClickHouse
        decompile: Whether we're in decompile mode
        yara_scan: Whether we're in YARA-only mode (checks yara_matches table)
        magika_filter: Magika filetype label (e.g. 'apk') to pick the right code table

    Returns:
        set: Set of SHA256 hashes that should be skipped
    """
    if not sha256_list:
        return set()

    # YARA-only mode: check yara_matches table for already-scanned samples
    if yara_scan:
        return _check_yara_matches_bulk(sha256_list)

    basic_table = f"{index_prefix}_basic_properties"
    batch_size = 3900

    try:
        client = settings.create_clickhouse_client()

        # Step 1: Check basic_properties (required for both modes)
        in_basic_properties = set()
        for i in range(0, len(sha256_list), batch_size):
            batch = sha256_list[i:i+batch_size]
            placeholders = "','".join(batch)
            query = f"SELECT DISTINCT sha256 FROM {basic_table} FINAL WHERE sha256 IN ('{placeholders}')"
            result = client.query(query)
            for row in result.result_rows:
                hash_value = row[0]
                if isinstance(hash_value, bytes):
                    hash_value = hash_value.decode('utf-8')
                in_basic_properties.add(hash_value)

        if not decompile:
            # Analysis mode: skip samples already in basic_properties
            return in_basic_properties

        # Decompile mode: skip samples NOT in basic_properties + already decompiled
        not_in_basic = set(sha256_list) - in_basic_properties

        if not in_basic_properties:
            return set(sha256_list)  # None ready for decompilation

        # Step 2: Check disassembled table for samples that ARE in basic_properties
        # Disassembly is the ground truth for code analysis (it always succeeds,
        # unlike decompilation which may fail).
        disassembled_table = _get_code_dedup_table(magika_filter)

        already_disassembled = set()
        samples_to_check = list(in_basic_properties)
        for i in range(0, len(samples_to_check), batch_size):
            batch = samples_to_check[i:i+batch_size]
            placeholders = "','".join(batch)
            query = f"SELECT DISTINCT sha256 FROM {disassembled_table} FINAL WHERE sha256 IN ('{placeholders}')"
            result = client.query(query)
            for row in result.result_rows:
                hash_value = row[0]
                if isinstance(hash_value, bytes):
                    hash_value = hash_value.decode('utf-8')
                already_disassembled.add(hash_value)

        return not_in_basic | already_disassembled

    except Exception as e:
        print(f"[ERROR] Failed to check hashes in DB: {e}")
        return set()
    finally:
        if 'client' in locals():
            client.close()