Repository: astropy/astroquery

Issue:
XMatch.query missing cat1 when usign two previously uploaded tables
Hi, I'm trying to match two previously uploaded catalogue this way:

    radec_mid = SkyCoord(a_mid, d_mid, unit=(u.deg, u.deg))
    columns = ['Gmag', 'BP-RP', 'RAJ2000', 'DEJ2000', 'ra', 'dec', 'pmra', 'mpdec', 'source_id']
    custom_visizer = Vizier(columns=columns)
    custom_visizer.ROW_LIMIT = -1

    astropy.utils.data.clear_download_cache(hashorurl=None, pkgname='astropy')
    
    stars_1 = custom_visizer.query_region(radec_mid, radius=self.telescope.fov, catalog='I/355/gaiadr3',
                                        **kwargs)
    stars_2 = custom_visizer.query_region(radec_mid, radius=self.telescope.fov, catalog='I/350/gaiaedr3',
                                          **kwargs)

    stars = XMatch.query(cat1=stars_1[0], cat2=stars_2[0], max_distance=1 * u.arcsec, colRA1='RAJ2000',
                         colDec1='DEJ2000', colRA2='RAJ2000', colDec2='RAJ2000')

I checked that the two tables are correctly uploaded, but the XMatch.query function gives me this error:
```
Message: Missing parameter "cat1".
                 Trace:
cds.xmatch.exception.CmdParseException: Missing parameter "cat1".
        at cds.xmatch.servlet.SyncXMatch.exec(SyncXMatch.java:656)
        at cds.xmatch.servlet.SyncXMatch.doPost(SyncXMatch.java:476)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:650)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:731)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:218)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:110)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:498)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:169)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
        at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:962)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:445)
        at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1091)
        at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:637)
        at org.apache.tomcat.util.net.AprEndpoint$SocketWithOptionsProcessor.run(AprEndpoint.java:2486)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1135)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.base/java.lang.Thread.run(Thread.java:844)
```

But, then I do it this way:

```
stars = XMatch.query(cat1=stars_1[0], cat2='vizier:I/350/gaiaedr3, max_distance=1 * u.arcsec, 
                    colRA1='RAJ2000', colDec1='DEJ2000' )
```

Everything works fine

Discussion:

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index b6e9cc50..aba8224a 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -217,6 +217,12 @@ mpc
 - Rename ``MPC.get_mpc_object_endpoint`` to ``MPC._get_mpc_object_endpoint`` to
   indicate that it is a private method. [#3089]
 
+xmatch
+^^^^^^
+
+- Fix xmatch query for two local tables. The second table was written over the first one,
+  resulting in a confusing "missing cat1" error. [#3116]
+
 
 0.4.7 (2024-03-08)
 ==================
diff --git a/astroquery/xmatch/core.py b/astroquery/xmatch/core.py
index 6d9768a3..57cc9549 100644
--- a/astroquery/xmatch/core.py
+++ b/astroquery/xmatch/core.py
@@ -42,7 +42,7 @@ class XMatchClass(BaseQuery):
             If the table is uploaded or accessed through a URL, it must be
             in VOTable or CSV format with the positions in J2000
             equatorial frame and as decimal degrees numbers.
-        cat2 : str or file
+        cat2 : str, file or `~astropy.table.Table`
             Identifier of the second table. Follows the same rules as *cat1*.
         max_distance : `~astropy.units.Quantity`
             Maximum distance to look for counterparts.
@@ -127,17 +127,22 @@ class XMatchClass(BaseQuery):
         catstr = 'cat{0}'.format(cat_index)
         if isinstance(cat, str):
             payload[catstr] = cat
-        elif isinstance(cat, Table):
-            # write the Table's content into a new, temporary CSV-file
-            # so that it can be pointed to via the `files` option
-            # file will be closed when garbage-collected
-            fp = StringIO()
-            cat.write(fp, format='ascii.csv')
-            fp.seek(0)
-            kwargs['files'] = {catstr: ('cat1.csv', fp.read())}
         else:
-            # assume it's a file-like object, support duck-typing
-            kwargs['files'] = {catstr: ('cat1.csv', cat.read())}
+            # create the dictionary of uploaded files
+            if "files" not in kwargs:
+                kwargs["files"] = {}
+            if isinstance(cat, Table):
+                # write the Table's content into a new, temporary CSV-file
+                # so that it can be pointed to via the `files` option
+                # file will be closed when garbage-collected
+
+                fp = StringIO()
+                cat.write(fp, format='ascii.csv')
+                fp.seek(0)
+                kwargs['files'].update({catstr: (f'cat{cat_index}.csv', fp.read())})
+            else:
+                # assume it's a file-like object, support duck-typing
+                kwargs['files'].update({catstr: (f'cat{cat_index}.csv', cat.read())})
 
         if not self.is_table_available(cat):
             if ((colRA is None) or (colDec is None)):


Test patch:
diff --git a/astroquery/xmatch/tests/test_xmatch.py b/astroquery/xmatch/tests/test_xmatch.py
index d16f1507..d1c9899a 100644
--- a/astroquery/xmatch/tests/test_xmatch.py
+++ b/astroquery/xmatch/tests/test_xmatch.py
@@ -104,3 +104,13 @@ def test_xmatch_query_cat1_table_local(monkeypatch):
         'errHalfMaj', 'errHalfMin', 'errPosAng', 'Jmag', 'Hmag', 'Kmag',
         'e_Jmag', 'e_Hmag', 'e_Kmag', 'Qfl', 'Rfl', 'X', 'MeasureJD']
     assert len(table) == 11
+
+
+def test_two_local_tables():
+    table1 = Table({'a': [1], 'b': [1], 'c': [1]})
+    table2 = Table({'a': [1], 'b': [1], 'c': [1]})
+    payload = XMatch().query(cat1=table1, cat2=table2,
+                             colRA1="a", colDec1="b", colRA2="a", colDec2="b",
+                             max_distance=1 * arcsec,
+                             get_query_payload=True)
+    assert 'cat1' in payload[1]["files"] and 'cat2' in payload[1]["files"]