Raja Appuswamy

64 papers A* 2A 3B 3C 10Misc 6Journal 18Unranked 21
YearRankTypeTitle / Venue / Authors
2026 J jnl
CoRR
Xavier Pic, Nimesh Pinnamaneni, Raja Appuswamy
2026 conf
SCA/HPC Asia
Ruiwen Wang, Philippe Fang, Chong Li, Thibaut Tachon, Raja Appuswamy
2025 C conf
PCS
Xavier Pic, Nimesh Pinnamaneni, Raja Appuswamy
2025 C conf
CLUSTER
Ruiwen Wang, Chong Li, Thibaut Tachon, Raja Appuswamy, Teng Su
2025 B conf
IEEE Big Data
Baya Dhouib, Raja Appuswamy
2025 conf
DSP
Xavier Pic, Raja Appuswamy
2025 conf
NPC (2)
Ruiwen Wang, Chong Li, Hongxing Wang, Raja Appuswamy, Yujie Yuan
2024 Misc conf
SYSTOR
Eugenio Marinelli, Yiqing Yan, Lorenzo Tattini, Virginie Magnone, Pascal Barbry, Raja Appuswamy
2024 J jnl
CoRR
Eugenio Marinelli, Yiqing Yan, Virginie Magnone, Pascal Barbry, Raja Appuswamy
2024 J jnl
CoRR
Maciej Besta, Robert Gerstenberger, Patrick Iff, Pournima Sonawane, Juan Gómez-Luna, Raghavendra Kanakagiri, Rui Min, Onur Mutlu, Torsten Hoefler, Raja Appuswamy, Aidan O'Mahony
2023 J jnl
Proc. VLDB Endow.
Eugenio Marinelli, Yiqing Yan, Virginie Magnone, Charlotte Dumargne, Pascal Barbry, Thomas Heinis, Raja Appuswamy
2023 conf
DSP
Eugenio Marinelli, Virginie Magnone, Marie-Charlotte Dumargne, Pascal Barbry, Raja Appuswamy
2022 J jnl
SIGMOD Rec.
Manos Athanassoulis, Peter Triantafillou, Raja Appuswamy, Rajesh Bordawekar, Badrish Chandramouli, Xuntao Cheng, Ioana Manolescu, Yannis Papakonstantinou, Nesime Tatbul
2022 J jnl
Trans. Large Scale Data Knowl. Centered Syst.
Eugenio Marinelli, Eddy Ghabach, Yiqing Yan, Thomas Bolbroe, Omer S. Sella, Thomas Heinis, Raja Appuswamy
2022 conf
IPDPS Workshops
Yiqing Yan, Nimisha Chaturvedi, Raja Appuswamy
2022 J jnl
SIGMOD Rec.
Raja Appuswamy
2021 J jnl
BMC Bioinform.
Yiqing Yan, Nimisha Chaturvedi, Raja Appuswamy
2021 conf
iPRES
Eugenio Marinelli, Eddy Ghabach, Thomas Bolbroe, Omer S. Sella, Thomas Heinis, Raja Appuswamy
2021 B conf
ICIP
Eva Gil San Antonio, Melpomeni Dimopoulou, Marc Antonini, Pascal Barbry, Raja Appuswamy
2021 C conf
VCIP
Giulio Franzese, Yiqing Yan, Giuseppe Serra, Ivan D'Onofrio, Raja Appuswamy, Pietro Michiardi
2021 J jnl
Signal Process. Image Commun.
Melpomeni Dimopoulou, Marc Antonini, Pascal Barbry, Raja Appuswamy
2021 conf
ADMS@VLDB
Eugenio Marinelli, Raja Appuswamy
2021 A conf
CIDR
Raja Appuswamy, Vincent Joguin
2021 C conf
DaMoN
Eugenio Marinelli, Raja Appuswamy
2020 J jnl
Proc. VLDB Endow.
Angelos-Christos G. Anadiotis, Raja Appuswamy, Anastasia Ailamaki, Ilan Bronshtein, Hillel Avni, David Dominguez-Sal, Shay Goikhman, Eliezer Levy
2020 Misc conf
ICASSP
Melpomeni Dimopoulou, Marc Antonini, Pascal Barbry, Raja Appuswamy
2020 J jnl
CoRR
Raja Appuswamy, Vincent Joguin
2019 conf
EUSIPCO
Melpomeni Dimopoulou, Marc Antonini, Pascal Barbry, Raja Appuswamy
2019 J jnl
CoRR
Melpomeni Dimopoulou, Marc Antonini, Pascal Barbry, Raja Appuswamy
2019 ch.
Encyclopedia of Big Data Technologies
Raja Appuswamy
2019 C conf
DaMoN
Bunjamin Memishi, Raja Appuswamy, Marcus Paradies
2019 J jnl
CoRR
Bunjamin Memishi, Raja Appuswamy, Marcus Paradies
2019 A* conf
ICDE
Panagiotis Sioulas, Periklis Chrysogelos, Manos Karpathiotakis, Raja Appuswamy, Anastasia Ailamaki
2019 J jnl
Proc. VLDB Endow.
Periklis Chrysogelos, Manos Karpathiotakis, Raja Appuswamy, Anastasia Ailamaki
2019 A conf
CIDR
Raja Appuswamy, Kevin Le Brigand, Pascal Barbry, Marc Antonini, Olivier Madderson, Paul S. Freemont, James McDonald, Thomas Heinis
2019 A* conf
ICDE
Matthaios Olma, Odysseas Papapetrou, Raja Appuswamy, Anastasia Ailamaki
2019 J jnl
Commun. ACM
Raja Appuswamy, Goetz Graefe, Renata Borovica-Gajic, Anastasia Ailamaki
2018 conf
IPDPS Workshops
Raja Appuswamy, Jacques Fellay, Nimisha Chaturvedi
2017 J jnl
Proc. VLDB Endow.
Raja Appuswamy, Angelos-Christos G. Anadiotis, Danica Porobic, Mustafa Iman, Anastasia Ailamaki
2017 A conf
CIDR
Raja Appuswamy, Manos Karpathiotakis, Danica Porobic, Anastasia Ailamaki
2017 conf
ADMS@VLDB
Raja Appuswamy, Renata Borovica-Gajic, Goetz Graefe, Anastasia Ailamaki
2016 J jnl
Proc. VLDB Endow.
Renata Borovica-Gajic, Raja Appuswamy, Anastasia Ailamaki
2016 conf
ADMS/IMDM@VLDB
Adam Dziedzic, Manos Karpathiotakis, Ioannis Alagiannis, Raja Appuswamy, Anastasia Ailamaki
2016 C conf
DaMoN
Danica Porobic, Pinar Tözün, Raja Appuswamy, Anastasia Ailamaki
2016 C conf
DaMoN
Utku Sirin, Raja Appuswamy, Anastasia Ailamaki
2015 C conf
DaMoN
Raja Appuswamy, Matthaios Olma, Anastasia Ailamaki
2014 conf
CANDAR
David C. van Moolenbroek, Raja Appuswamy, Andrew S. Tanenbaum
2014 conf
HotStorage
Raja Appuswamy, Sergey Legtchenko, Antony I. T. Rowstron
2014 Misc conf
SYSTOR
David C. van Moolenbroek, Raja Appuswamy, Andrew S. Tanenbaum
2013 conf
LADC
David C. van Moolenbroek, Raja Appuswamy, Andrew S. Tanenbaum
2013 Misc conf
MSST
Raja Appuswamy, David C. van Moolenbroek, Andrew S. Tanenbaum
2013 B conf
ICPADS
Raja Appuswamy, David C. van Moolenbroek, Sharan Santhanam, Andrew S. Tanenbaum
2013 conf
SoCC
Raja Appuswamy, Christos Gkantsidis, Dushyanth Narayanan, Orion Hodson, Antony I. T. Rowstron
2013 C conf
PRDC
David C. van Moolenbroek, Raja Appuswamy, Andrew S. Tanenbaum
2012 conf
NAS
David C. van Moolenbroek, Raja Appuswamy, Andrew S. Tanenbaum
2012 Misc conf
MSST
Raja Appuswamy, David C. van Moolenbroek, Andrew S. Tanenbaum
2011 conf
NAS
Richard van Heuven van Staereling, Raja Appuswamy, David C. van Moolenbroek, Andrew S. Tanenbaum
2011 Misc conf
MSST
Raja Appuswamy, David C. van Moolenbroek, Andrew S. Tanenbaum
2011 conf
DSN Workshops
David C. van Moolenbroek, Raja Appuswamy, Andrew S. Tanenbaum
2010 conf
HotStorage
Raja Appuswamy, David C. van Moolenbroek, Andrew S. Tanenbaum
2010 C conf
PRDC
Raja Appuswamy, David C. van Moolenbroek, Andrew S. Tanenbaum
2010 J jnl
login Usenix Mag.
Andrew S. Tanenbaum, Raja Appuswamy, Herbert Bos, Lorenzo Cavallaro, Cristiano Giuffrida, Tomás Hrubý, Jorrit N. Herder, Erik van der Kouwe, David C. van Moolenbroek
2009 conf
LADC
Jorrit N. Herder, David C. van Moolenbroek, Raja Appuswamy, Bingzheng Wu, Ben Gras, Andrew S. Tanenbaum
2007 conf
Web3D
James Oliverio, Yvonne R. Masakowski, Howard Beck, Raja Appuswamy
redb/extractors/decompiler/DecompileAPK.py
← Index redb/extractors/decompiler/DecompileAPK.py python
"""APK Code Analysis Extractor.

Decompiles and disassembles APK DEX bytecode at the method level,
producing per-method content and reference records analogous to
the Binary Ninja code_binja_* tables.

Uses androguard + JADX + apktool to replicate Binary Ninja analysis
depth for Android applications.
"""

import gc
import hashlib
import inspect
import logging
import os
import threading
import time
from datetime import datetime, timezone
from typing import Any, Dict, Optional

from redb.extractors.decompiler.apk.analyzer import APKCodeAnalyzer
from redb.extractors.enum import Tag
from redb.extractors.extractor import Extractor


class DecompileAPK(Extractor):
    """APK code analysis extractor — produces multi-table ClickHouse export.

    Follows the same pattern as DecompileBinja for consistency.
    """

    def __init__(
        self,
        filepath,
        log,
        exporters=None,
        index_prefix=None,
        known_benign=False,
        known_malicious=False,
        filetype=None,
        decompile_modules=None,
    ):
        super().__init__(
            filepath,
            log,
            exporters,
            index_prefix,
            known_benign=known_benign,
            known_malicious=known_malicious,
        )
        self.log.debug(inspect.currentframe().f_code.co_name)
        self.analysis_results = None
        self.analyzer = None
        self.filetype = filetype or "apk"
        self.decompile_modules = decompile_modules or {"all"}

        try:
            self.APK_DECOMPILE_TIMEOUT = int(
                os.getenv("APK_DECOMPILE_TIMEOUT", "600")
            )
        except ValueError:
            self.log.warning(
                "Invalid APK_DECOMPILE_TIMEOUT value, using default of 600 seconds"
            )
            self.APK_DECOMPILE_TIMEOUT = 600

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.cleanup_run()

    def calculate_md5(self, input_str):
        """Calculate MD5 hash of a string."""
        return hashlib.md5(input_str.encode("utf-8")).hexdigest()

    def cleanup_run(self):
        """Clean up after analysis."""
        try:
            if self.analyzer:
                self.analyzer.cleanup()
                self.analyzer = None
            gc.collect()
        except Exception as e:
            self.log.error(f"Error in cleanup: {e}")

    def analyze_apk(self) -> Optional[Dict[str, Any]]:
        """Run APK code analysis and return results."""
        self.log.debug("Starting APK code analysis")
        try:
            self.analyzer = APKCodeAnalyzer(
                filepath=self.filepath,
                timeout=self.APK_DECOMPILE_TIMEOUT,
                log=self.log,
                decompile_modules=self.decompile_modules,
            )
            results = self.analyzer.extract()
            return results
        except Exception as e:
            self.log.error(f"Error in APK code analysis: {e}")
            import traceback
            self.log.error(f"Traceback: {traceback.format_exc()}")
            return None
        finally:
            self.cleanup_run()

    def extract(self):
        """Extract and process all analysis results.

        Uses daemon thread with timeout, same pattern as DecompileBinja.
        """
        self.log.debug(inspect.currentframe().f_code.co_name)

        extraction_completed = False
        extraction_result = False
        extraction_error = None

        def do_extraction():
            nonlocal extraction_completed, extraction_result, extraction_error
            try:
                results = self.analyze_apk()
                if not results:
                    extraction_result = False
                else:
                    self.analysis_results = results
                    self.analysis_results["sha256"] = self.sha256
                    self.analysis_results["sha1"] = self.sha1
                    self.analysis_results["md5"] = self.md5
                    extraction_result = True
            except Exception as e:
                extraction_error = e
                extraction_result = False
            finally:
                extraction_completed = True

        extraction_thread = threading.Thread(target=do_extraction)
        extraction_thread.daemon = True
        extraction_thread.start()

        start_time = time.time()
        while (
            not extraction_completed
            and (time.time() - start_time) < self.APK_DECOMPILE_TIMEOUT
        ):
            time.sleep(1)

        if not extraction_completed:
            self.log.error(
                f"APK extraction timed out after {self.APK_DECOMPILE_TIMEOUT} seconds"
            )
            self.cleanup_run()
            return None

        if extraction_error:
            self.log.error(f"Error in APK extraction: {extraction_error}")
            return None

        return self.analysis_results if extraction_result else None

    def prepare_export_data(self, exporter_type: str) -> Any:
        """Prepare data for database export."""
        self.log.debug(inspect.currentframe().f_code.co_name)
        if not self.analysis_results:
            return None

        if exporter_type == "ClickHouseExporter":
            now = datetime.now(timezone.utc)
            export = {"multi_table": True}

            # Table 1: Decompiled method content
            if self.analysis_results.get("decompiled_content"):
                export["decompiled_content"] = {
                    "table": "code_apk_decompiled_methods_content",
                    "data": [
                        [
                            f["decompiled_method_hash"],
                            f["decompiled_method"],
                            f.get("decompiled_method_type", "UNKNOWN"),
                            1 if f.get("decompiled_has_string_encryption") else 0,
                            1 if f.get("decompiled_has_reflection_calls") else 0,
                            1 if f.get("decompiled_excessive_goto_count") else 0,
                            now,
                        ]
                        for f in self.analysis_results["decompiled_content"]
                    ],
                    "column_names": [
                        "decompiled_method_hash",
                        "decompiled_method",
                        "decompiled_method_type",
                        "decompiled_has_string_encryption",
                        "decompiled_has_reflection_calls",
                        "decompiled_excessive_goto_count",
                        "analysis_date",
                    ],
                    "column_type_names": [
                        "FixedString(64)",
                        "String",
                        "Enum8('USER'=1, 'LIBRARY'=2, 'UNKNOWN'=5)",
                        "UInt8",
                        "UInt8",
                        "UInt8",
                        "DateTime64(3, 'UTC')",
                    ],
                }

            # Table 2: Decompiled method references
            if self.analysis_results.get("decompiled_refs"):
                export["decompiled_refs"] = {
                    "table": "code_apk_decompiled_methods_references",
                    "data": [
                        [
                            self.analysis_results["sha256"],
                            f["decompiled_method_hash"],
                            f.get("smali_method_hash"),
                            f.get("decompiled_class_name", ""),
                            f.get("decompiled_method_name", ""),
                            f.get("decompiled_method_signature", ""),
                            f.get("decompiled_method_prototype", ""),
                            f.get("functions_caller", []),
                            f.get("functions_call", []),
                            now,
                        ]
                        for f in self.analysis_results["decompiled_refs"]
                    ],
                    "column_names": [
                        "sha256",
                        "decompiled_method_hash",
                        "smali_method_hash",
                        "decompiled_class_name",
                        "decompiled_method_name",
                        "decompiled_method_signature",
                        "decompiled_method_prototype",
                        "functions_caller",
                        "functions_call",
                        "analysis_date",
                    ],
                    "column_type_names": [
                        "FixedString(64)",
                        "FixedString(64)",
                        "Nullable(FixedString(64))",
                        "LowCardinality(String)",
                        "LowCardinality(String)",
                        "String",
                        "String",
                        "Array(String)",
                        "Array(String)",
                        "DateTime64(3, 'UTC')",
                    ],
                }

            # Table 3: Smali method content
            if self.analysis_results.get("smali_content"):
                export["smali_content"] = {
                    "table": "code_apk_smali_methods_content",
                    "data": [
                        [
                            f["smali_method_hash"],
                            f["smali_method"],
                            f.get("smali_method_type", "UNKNOWN"),
                            f.get("smali_instructions_count", 0),
                            f.get("smali_register_count", 0),
                            1 if f.get("smali_has_string_encryption") else 0,
                            1 if f.get("smali_has_reflection_calls") else 0,
                            1 if f.get("smali_excessive_goto_count") else 0,
                            f.get("smali_flattened_score", 0.0),
                            f.get("smali_mba_score", 0.0),
                            now,
                        ]
                        for f in self.analysis_results["smali_content"]
                    ],
                    "column_names": [
                        "smali_method_hash",
                        "smali_method",
                        "smali_method_type",
                        "smali_instructions_count",
                        "smali_register_count",
                        "smali_has_string_encryption",
                        "smali_has_reflection_calls",
                        "smali_excessive_goto_count",
                        "smali_flattened_score",
                        "smali_mba_score",
                        "analysis_date",
                    ],
                    "column_type_names": [
                        "FixedString(64)",
                        "String",
                        "Enum8('USER'=1, 'LIBRARY'=2, 'UNKNOWN'=5)",
                        "UInt32",
                        "UInt16",
                        "UInt8",
                        "UInt8",
                        "UInt8",
                        "Float64",
                        "Float64",
                        "DateTime64(3, 'UTC')",
                    ],
                }

            # Table 4: Smali method references
            if self.analysis_results.get("smali_refs"):
                export["smali_refs"] = {
                    "table": "code_apk_smali_methods_references",
                    "data": [
                        [
                            self.analysis_results["sha256"],
                            f["smali_method_hash"],
                            f.get("decompiled_method_hash"),
                            f.get("smali_class_name", ""),
                            f.get("smali_method_name", ""),
                            f.get("smali_method_signature", ""),
                            now,
                        ]
                        for f in self.analysis_results["smali_refs"]
                    ],
                    "column_names": [
                        "sha256",
                        "smali_method_hash",
                        "decompiled_method_hash",
                        "smali_class_name",
                        "smali_method_name",
                        "smali_method_signature",
                        "analysis_date",
                    ],
                    "column_type_names": [
                        "FixedString(64)",
                        "FixedString(64)",
                        "Nullable(FixedString(64))",
                        "LowCardinality(String)",
                        "LowCardinality(String)",
                        "String",
                        "DateTime64(3, 'UTC')",
                    ],
                }

            # Table 5: Method similarity metrics (content-based fuzzy matching)
            if self.analysis_results.get("similarity_metrics"):
                export["method_similarity_metrics"] = {
                    "table": "code_apk_method_similarity_metrics",
                    "data": [
                        [
                            f["smali_method_hash"],
                            f.get("ssdeep_smali"),
                            f.get("tlsh_smali"),
                            f.get("ssdeep_smali_normalized"),
                            f.get("tlsh_smali_normalized"),
                            f.get("minhash", []),
                            now,
                        ]
                        for f in self.analysis_results["similarity_metrics"]
                    ],
                    "column_names": [
                        "smali_method_hash",
                        "ssdeep_smali",
                        "tlsh_smali",
                        "ssdeep_smali_normalized",
                        "tlsh_smali_normalized",
                        "minhash",
                        "analysis_date",
                    ],
                    "column_type_names": [
                        "FixedString(64)",
                        "Nullable(String)",
                        "Nullable(FixedString(72))",
                        "Nullable(String)",
                        "Nullable(FixedString(72))",
                        "Array(UInt8)",
                        "DateTime64(3, 'UTC')",
                    ],
                }

            # Table 5b: CFG method features (structural/topological)
            if self.analysis_results.get("cfg"):
                export["cfg_methods"] = {
                    "table": "code_apk_cfg_methods",
                    "data": [
                        [
                            cfg["smali_method_hash"],
                            cfg["cfg_topology_hash"],
                            cfg["block_count"],
                            cfg["edge_count"],
                            cfg.get("cfg_instructions_count", 0),
                            cfg.get("call_count", 0),
                            cfg["cyclomatic_complexity"],
                            cfg.get("loop_count", 0),
                            cfg.get("max_depth", 0),
                            cfg.get("max_fan_out", 0),
                            cfg.get("md_index_topdown", 0),
                            cfg.get("md_index_bottomup", 0),
                            cfg.get("prime_product_smali", 0),
                            cfg.get("cfg_feature_tlsh"),
                            cfg.get("wl_minhash", []),
                            cfg.get("bb_features", []),
                            cfg.get("cfg_adjacency", []),
                            now,
                        ]
                        for cfg in self.analysis_results["cfg"]
                        if cfg is not None
                    ],
                    "column_names": [
                        "smali_method_hash",
                        "cfg_topology_hash",
                        "block_count",
                        "edge_count",
                        "cfg_instructions_count",
                        "call_count",
                        "cyclomatic_complexity",
                        "loop_count",
                        "max_depth",
                        "max_fan_out",
                        "md_index_topdown",
                        "md_index_bottomup",
                        "prime_product_smali",
                        "cfg_feature_tlsh",
                        "wl_minhash",
                        "bb_features",
                        "cfg_adjacency",
                        "analysis_date",
                    ],
                    "column_type_names": [
                        "FixedString(64)",
                        "FixedString(16)",
                        "UInt16",
                        "UInt16",
                        "UInt32",
                        "UInt16",
                        "UInt16",
                        "UInt16",
                        "UInt16",
                        "UInt16",
                        "UInt64",
                        "UInt64",
                        "UInt64",
                        "Nullable(FixedString(72))",
                        "Array(UInt8)",
                        "Array(Array(UInt16))",
                        "Array(UInt32)",
                        "DateTime64(3, 'UTC')",
                    ],
                }

            # Table 6: Strings — reuse code_binja_strings_raw for cross-format correlation
            # DEX strings are MUTF-8; string_raw = string since no encoding difference
            if self.analysis_results.get("strings"):
                export["strings_raw"] = {
                    "table": "code_binja_strings_raw",
                    "data": [
                        [
                            self.analysis_results["sha256"],
                            s["string"],
                            s["string"],  # string_raw = string (MUTF-8 decoded to UTF-8)
                            s.get("string_encoding", "UTF8"),
                            s.get("string_offset", 0),
                            s.get("string_length", len(s["string"])),
                            s.get("string_length", len(s["string"])),  # string_raw_length = string_length
                            s.get("string_entropy", 0.0),
                        ]
                        for s in self.analysis_results["strings"]
                    ],
                    "column_names": [
                        "sha256",
                        "string",
                        "string_raw",
                        "string_encoding",
                        "string_offset",
                        "string_length",
                        "string_raw_length",
                        "string_entropy",
                    ],
                    "column_type_names": [
                        "FixedString(64)",
                        "String",
                        "String",
                        "LowCardinality(String)",
                        "UInt64",
                        "UInt32",
                        "UInt32",
                        "Float32",
                    ],
                }

            # Table 7: Analysis errors
            if self.analysis_results.get("analysis_errors"):
                export["analysis_errors"] = {
                    "table": "code_apk_analysis_errors",
                    "data": [
                        [
                            self.analysis_results["sha256"],
                            f.get("class_name"),
                            f.get("method_name"),
                            f.get("error_location", "unknown"),
                            f.get("error_message", ""),
                            f.get("error_type", "unknown"),
                            self.calculate_md5(
                                f"{f.get('error_message', '')}"
                                f"{f.get('class_name', '')}"
                                f"{f.get('method_name', '')}"
                                f"{f.get('error_location', 'unknown')}"
                            ),
                            "new",
                            now,
                        ]
                        for f in self.analysis_results["analysis_errors"]
                    ],
                    "column_names": [
                        "sha256",
                        "class_name",
                        "method_name",
                        "error_location",
                        "error_message",
                        "error_type",
                        "error_hash",
                        "status",
                        "analysis_date",
                    ],
                    "column_type_names": [
                        "FixedString(64)",
                        "Nullable(String)",
                        "Nullable(String)",
                        "LowCardinality(String)",
                        "Nullable(String)",
                        "Nullable(String)",
                        "FixedString(32)",
                        "Enum8('new'=1, 'investigating'=2, 'fixed'=3, 'wontfix'=4)",
                        "DateTime64(3, 'UTC')",
                    ],
                }

            return export

        return None

    def tag(self) -> str:
        """Return the tag for this extractor."""
        return Tag.APK_DECOMPILED.value

    def get_clickhouse_table(self) -> str:
        """Not used directly as we're handling multiple tables."""
        pass