diff --git a/ChangeLog b/ChangeLog index e5667c23..4b33ac12 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,22 @@ +2005-03-01 Federico Di Gregorio + + * psycopg/microprotocols.c (microprotocols_adapt): fixed small + typo that made adaptation using __conform__ impossible. + +2005-02-28 Federico Di Gregorio + + * lib/extras.py: removed AsIs adapter (now a built-in); also + removed prepare() method from the adapters that don't use it to + avoid an extra method call at mogrification time. + + * psycopg/psycopgmodule.c (psyco_adapters_init): added + initialization of the AsIs adapter (adapts int, long, float and + *wonder* None!) + + * psycopg/cursor_type.c (_mogrify_getquoted): reorganized the code + to adapt and then call .getquoted() to obtain the quoted data into + this new function. + 2005-2-27 Federico Di Gregorio * examples/myfirstrecipe.py: fixed adapter registration. diff --git a/NEWS b/NEWS index 7209bc2c..f8035403 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,8 @@ What's new in psycopg 1.99.12 connect to an old protocol 2 database and will detect it and use the right code. +* getquoted() called for real by the mogrification code. + What's new in psycopg 1.99.11 ----------------------------- diff --git a/examples/usercast.py b/examples/usercast.py index 27117090..854e2770 100644 --- a/examples/usercast.py +++ b/examples/usercast.py @@ -23,6 +23,12 @@ import sys, psycopg import psycopg.extensions import whrandom +# importing psycopg.extras will give us a nice tuple adapter: this is wrong +# because the adapter is meant to be used in SQL IN clauses while we use +# tuples to represent points but it works and the example is about Rect, not +# "Point" +import psycopg.extras + if len(sys.argv) > 1: DSN = sys.argv[1] @@ -43,7 +49,7 @@ conn.commit() # usually a function, but we use a class, just to demonstrate the # flexibility of the psycopg casting system -class Rect: +class Rect(object): """Very simple rectangle. Note that we use this type as a data holder, as an adapter of itself for @@ -59,7 +65,6 @@ class Rect: def __conform__(self, proto): """This is a terrible hack, just ignore proto and return self.""" - print "CONFORMIG!" return self def from_points(self, x0, y0, x1, y1): @@ -82,11 +87,6 @@ class Rect: self.x, self.y, self.x + self.width, self.y + self.height) return s - __str__ = getquoted - - def prepare(self, conn): - pass - def show(self): """Format a description of the box.""" s = "X: %d\tY: %d\tWidth: %d\tHeight: %d" % ( diff --git a/lib/extras.py b/lib/extras.py index 1013bec9..1d523d99 100644 --- a/lib/extras.py +++ b/lib/extras.py @@ -71,40 +71,19 @@ class DictRow(list): -class AsIs(object): - """An adapter that just return the object 'as is'. - - psycopg 1.99.9 has some optimizations that make impossible to call adapt() - without adding some basic adapters externally. This limitation will be - lifted in a future release. In the meantime you can use the AsIs adapter. - """ - def __init__(self, obj): - self.__obj = obj - def getquoted(self): - return str(self.__obj) - def prepare(self, conn): - pass - __str__ = getquoted - class SQL_IN(object): """Adapt any iterable to an SQL quotable object.""" def __init__(self, seq): self._seq = seq - def prepare(self, conn): - pass - def getquoted(self): # this is the important line: note how every object in the # list is adapted and then how getquoted() is called on it qobjs = [str(_A(o).getquoted()) for o in self._seq] return '(' + ', '.join(qobjs) + ')' - + __str__ = getquoted - - + _RA(tuple, SQL_IN) -_RA(int, AsIs) -_RA(float, AsIs) diff --git a/psycopg/adapter_asis.c b/psycopg/adapter_asis.c new file mode 100644 index 00000000..5e5fb861 --- /dev/null +++ b/psycopg/adapter_asis.c @@ -0,0 +1,223 @@ +/* adapter_asis.c - adapt types as they are + * + * Copyright (C) 2003-2004 Federico Di Gregorio + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include + +#define PSYCOPG_MODULE +#include "psycopg/config.h" +#include "psycopg/python.h" +#include "psycopg/psycopg.h" +#include "psycopg/adapter_asis.h" + + +/** the AsIs object **/ + +static PyObject * +asis_str(asisObject *self) +{ + if (self->wrapped == Py_None) { + return PyString_FromString("NULL"); + } + else { + return PyObject_Repr(self->wrapped); + } +} + +PyObject * +asis_getquoted(asisObject *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) return NULL; + return asis_str(self); +} + +PyObject * +asis_prepare(asisObject *self, PyObject *args) +{ + PyObject *fake; + + if (!PyArg_ParseTuple(args, "O", &fake)) return NULL; + + Py_INCREF(Py_None); + return Py_None; +} + +/** the AsIs object */ + +/* object member list */ + +static struct PyMemberDef asisObject_members[] = { + {"adapted", T_OBJECT, offsetof(asisObject, wrapped), RO}, + {NULL} +}; + +/* object method table */ + +static PyMethodDef asisObject_methods[] = { + {"getquoted", (PyCFunction)asis_getquoted, METH_VARARGS, + "getquoted() -> wrapped object value as SQL-quoted string"}, + /* {"prepare", (PyCFunction)asis_prepare, METH_VARARGS, + "prepare(conn) -> currently does nothing"}, */ + {NULL} /* Sentinel */ +}; + +/* initialization and finalization methods */ + +static int +asis_setup(asisObject *self, PyObject *obj) +{ + Dprintf("asis_setup: init asis object at %p, refcnt = %d", + self, ((PyObject *)self)->ob_refcnt); + + self->wrapped = obj; + Py_INCREF(self->wrapped); + + Dprintf("asis_setup: good asis object at %p, refcnt = %d", + self, ((PyObject *)self)->ob_refcnt); + return 0; +} + +static void +asis_dealloc(PyObject* obj) +{ + asisObject *self = (asisObject *)obj; + + Py_XDECREF(self->wrapped); + + Dprintf("asis_dealloc: deleted asis object at %p, refcnt = %d", + obj, obj->ob_refcnt); + + obj->ob_type->tp_free(obj); +} + +static int +asis_init(PyObject *obj, PyObject *args, PyObject *kwds) +{ + PyObject *o; + + if (!PyArg_ParseTuple(args, "O", &o)) + return -1; + + return asis_setup((asisObject *)obj, o); +} + +static PyObject * +asis_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + return type->tp_alloc(type, 0); +} + +static void +asis_del(PyObject* self) +{ + PyObject_Del(self); +} + +static PyObject * +asis_repr(asisObject *self) +{ + return PyString_FromFormat("", self); +} + + +/* object type */ + +#define asisType_doc \ +"psycopg.AsIs(str) -> new AsIs adapter object" + +PyTypeObject asisType = { + PyObject_HEAD_INIT(NULL) + 0, + "psycopg._psycopg.AsIs", + sizeof(asisObject), + 0, + asis_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + + 0, /*tp_compare*/ + + (reprfunc)asis_repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + + 0, /*tp_call*/ + (reprfunc)asis_str, /*tp_str*/ + + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ + asisType_doc, /*tp_doc*/ + + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + + /* Attribute descriptor and subclassing stuff */ + + asisObject_methods, /*tp_methods*/ + asisObject_members, /*tp_members*/ + 0, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + + asis_init, /*tp_init*/ + PyType_GenericAlloc, /*tp_alloc*/ + asis_new, /*tp_new*/ + (freefunc)asis_del, /*tp_free Low-level free-memory routine */ + 0, /*tp_is_gc For PyObject_IS_GC */ + 0, /*tp_bases*/ + 0, /*tp_mro method resolution order */ + 0, /*tp_cache*/ + 0, /*tp_subclasses*/ + 0 /*tp_weaklist*/ +}; + + +/** module-level functions **/ + +PyObject * +psyco_AsIs(PyObject *module, PyObject *args) +{ + PyObject *obj; + + if (!PyArg_ParseTuple(args, "O", &obj)) + return NULL; + + return PyObject_CallFunction((PyObject *)&asisType, "O", obj); +} diff --git a/psycopg/adapter_asis.h b/psycopg/adapter_asis.h new file mode 100644 index 00000000..79e7ec44 --- /dev/null +++ b/psycopg/adapter_asis.h @@ -0,0 +1,51 @@ +/* adapter_asis.h - definition for the psycopg AsIs type wrapper + * + * Copyright (C) 2003-2005 Federico Di Gregorio + * + * This file is part of psycopg. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef PSYCOPG_ASIS_H +#define PSYCOPG_ASIS_H 1 + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern PyTypeObject asisType; + +typedef struct { + PyObject HEAD; + + /* this is the real object we wrap */ + PyObject *wrapped; + +} asisObject; + +/* functions exported to psycopgmodule.c */ + +extern PyObject *psyco_AsIs(PyObject *module, PyObject *args); +#define psyco_AsIs_doc \ + "psycopg.AsIs(obj) -> new AsIs wrapper object" + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(PSYCOPG_ASIS_H) */ diff --git a/psycopg/adapter_binary.c b/psycopg/adapter_binary.c index d8629d4d..43be441e 100644 --- a/psycopg/adapter_binary.c +++ b/psycopg/adapter_binary.c @@ -190,8 +190,8 @@ static struct PyMemberDef binaryObject_members[] = { static PyMethodDef binaryObject_methods[] = { {"getquoted", (PyCFunction)binary_getquoted, METH_VARARGS, "getquoted() -> wrapped object value as SQL-quoted binary string"}, - {"prepare", (PyCFunction)binary_prepare, METH_VARARGS, - "prepare(conn) -> currently does nothing"}, + /* {"prepare", (PyCFunction)binary_prepare, METH_VARARGS, + "prepare(conn) -> currently does nothing"},*/ {NULL} /* Sentinel */ }; diff --git a/psycopg/adapter_datetime.c b/psycopg/adapter_datetime.c index 36051b59..2f3b70ac 100644 --- a/psycopg/adapter_datetime.c +++ b/psycopg/adapter_datetime.c @@ -108,8 +108,8 @@ static struct PyMemberDef pydatetimeObject_members[] = { static PyMethodDef pydatetimeObject_methods[] = { {"getquoted", (PyCFunction)pydatetime_getquoted, METH_VARARGS, "getquoted() -> wrapped object value as SQL date/time"}, - {"prepare", (PyCFunction)pydatetime_prepare, METH_VARARGS, - "prepare(conn) -> currently does nothing"}, + /* {"prepare", (PyCFunction)pydatetime_prepare, METH_VARARGS, + "prepare(conn) -> currently does nothing"}, */ {NULL} /* Sentinel */ }; diff --git a/psycopg/adapter_mxdatetime.c b/psycopg/adapter_mxdatetime.c index 6b4e0c36..1c83e0ca 100644 --- a/psycopg/adapter_mxdatetime.c +++ b/psycopg/adapter_mxdatetime.c @@ -84,8 +84,8 @@ static struct PyMemberDef mxdatetimeObject_members[] = { static PyMethodDef mxdatetimeObject_methods[] = { {"getquoted", (PyCFunction)mxdatetime_getquoted, METH_VARARGS, "getquoted() -> wrapped object value as SQL date/time"}, - {"prepare", (PyCFunction)mxdatetime_prepare, METH_VARARGS, - "prepare(conn) -> currently does nothing"}, + /* {"prepare", (PyCFunction)mxdatetime_prepare, METH_VARARGS, + "prepare(conn) -> currently does nothing"}, */ {NULL} /* Sentinel */ }; diff --git a/psycopg/adapter_pboolean.c b/psycopg/adapter_pboolean.c index 268c9386..a58aecdb 100644 --- a/psycopg/adapter_pboolean.c +++ b/psycopg/adapter_pboolean.c @@ -76,8 +76,8 @@ static struct PyMemberDef pbooleanObject_members[] = { static PyMethodDef pbooleanObject_methods[] = { {"getquoted", (PyCFunction)pboolean_getquoted, METH_VARARGS, "getquoted() -> wrapped object value as SQL-quoted string"}, - {"prepare", (PyCFunction)pboolean_prepare, METH_VARARGS, - "prepare(conn) -> currently does nothing"}, + /* {"prepare", (PyCFunction)pboolean_prepare, METH_VARARGS, + "prepare(conn) -> currently does nothing"}, */ {NULL} /* Sentinel */ }; diff --git a/psycopg/cursor_type.c b/psycopg/cursor_type.c index 341f2759..330b6d85 100644 --- a/psycopg/cursor_type.c +++ b/psycopg/cursor_type.c @@ -61,6 +61,38 @@ psyco_curs_close(cursorObject *self, PyObject *args) /* mogrify a query string and build argument array or dict */ +static PyObject* +_mogrify_getquoted(PyObject *obj, connectionObject *conn) +{ + PyObject *res = NULL; + PyObject *tmp = microprotocols_adapt( + obj, (PyObject*)&isqlquoteType, NULL); + + if (tmp != NULL) { + Dprintf("_mogrify: adapted to %s", tmp->ob_type->tp_name); + + /* if requested prepare the object passing it the connection */ + if (PyObject_HasAttrString(tmp, "prepare")) { + res = PyObject_CallMethod(tmp, "prepare", "O", (PyObject*)conn); + if (res == NULL) { + Py_DECREF(tmp); + return NULL; + } + else { + Py_DECREF(res); + } + } + + /* call the getquoted method on tmp (that should exist because we + adapted to the right protocol) */ + res = PyObject_CallMethod(tmp, "getquoted", NULL); + Py_DECREF(tmp); + } + + /* we return res with one extra reference, the caller shall free it */ + return res; +} + static int _mogrify(PyObject *var, PyObject *fmt, connectionObject *conn, PyObject **new) { @@ -131,28 +163,17 @@ _mogrify(PyObject *var, PyObject *fmt, connectionObject *conn, PyObject **new) if (*d) *d = 's'; } else { - t = microprotocols_adapt(value, - (PyObject*)&isqlquoteType, - NULL); + t = _mogrify_getquoted(value, conn); + if (t != NULL) { - /* t is a new object, refcnt = 1 */ - Dprintf("_mogrify: adapted to %s", - t->ob_type->tp_name); - - /* prepare the object passing it the connection */ - PyObject_CallMethod(t, "prepare", "O", - (PyObject*)conn); - PyDict_SetItem(n, key, t); /* both key and t refcnt +1, key is at 2 now */ } else { - /* we did not found an adapter but we don't raise - an exception; just pass the original value */ - PyErr_Clear(); - PyDict_SetItem(n, key, value); - Dprintf("_mogrify: set value refcnt: %d", - value->ob_refcnt); + /* no adapter found, raise a BIG exception */ + Py_XDECREF(value); + Py_DECREF(n); + return -1; } } @@ -200,20 +221,16 @@ _mogrify(PyObject *var, PyObject *fmt, connectionObject *conn, PyObject **new) Py_DECREF(value); } else { - PyObject *t = microprotocols_adapt(value, - (PyObject*)&isqlquoteType, - NULL); - if (t != NULL) { - /* prepare the object passing it the connection */ - PyObject_CallMethod(t, "prepare", "O", (PyObject*)conn); + PyObject *t = _mogrify_getquoted(value, conn); + if (t != NULL) { PyTuple_SET_ITEM(n, index, t); Py_DECREF(value); } else { - PyErr_Clear(); - PyTuple_SET_ITEM(n, index, value); - /* here we steal value ref, no need to DECREF */ + Py_DECREF(n); + Py_DECREF(value); + return -1; } } c = d; @@ -228,7 +245,7 @@ _mogrify(PyObject *var, PyObject *fmt, connectionObject *conn, PyObject **new) n = PyTuple_New(0); *new = n; - return 0;; + return 0; } #define psyco_curs_execute_doc \ diff --git a/psycopg/microprotocols.c b/psycopg/microprotocols.c index c9c2af82..9675d914 100644 --- a/psycopg/microprotocols.c +++ b/psycopg/microprotocols.c @@ -58,8 +58,7 @@ microprotocols_add(PyTypeObject *type, PyObject *proto, PyObject *cast) { if (proto == NULL) proto = (PyObject*)&isqlquoteType; - Dprintf("microprotocols_add: cast %p for (%s, ?)", - cast, type->tp_name); + Dprintf("microprotocols_add: cast %p for (%s, ?)", cast, type->tp_name); PyDict_SetItem(psyco_adapters, Py_BuildValue("(OO)", (PyObject*)type, proto), @@ -78,6 +77,8 @@ microprotocols_adapt(PyObject *obj, PyObject *proto, PyObject *alt) because the ISQLQuote type is abstract and there is no way to get a quotable object to be its instance */ + Dprintf("microprotocols_adapt: trying to adapt %s", obj->ob_type->tp_name); + /* look for an adapter in the registry */ key = Py_BuildValue("(OO)", (PyObject*)obj->ob_type, proto); adapter = PyDict_GetItem(psyco_adapters, key); @@ -95,14 +96,14 @@ microprotocols_adapt(PyObject *obj, PyObject *proto, PyObject *alt) return NULL; } - /* and finally try to have the object adapt itself */ + /* and finally try to have the object adapt itself */ if (PyObject_HasAttrString(obj, "__conform__")) { - PyObject *adapted = PyObject_CallMethod(proto, "__conform__","O", obj); + PyObject *adapted = PyObject_CallMethod(obj, "__conform__","O", proto); if (adapted && adapted != Py_None) return adapted; if (PyErr_Occurred() && !PyErr_ExceptionMatches(PyExc_TypeError)) return NULL; } - + /* else set the right exception and return NULL */ PyErr_SetString(ProgrammingError, "can't adapt"); return NULL; diff --git a/psycopg/microprotocols_proto.c b/psycopg/microprotocols_proto.c index 31cd4cda..861c8efa 100644 --- a/psycopg/microprotocols_proto.c +++ b/psycopg/microprotocols_proto.c @@ -106,8 +106,8 @@ static struct PyMethodDef isqlquoteObject_methods[] = { METH_VARARGS, psyco_isqlquote_getbinary_doc}, {"getbuffer", (PyCFunction)psyco_isqlquote_getbuffer, METH_VARARGS, psyco_isqlquote_getbuffer_doc}, - {"prepare", (PyCFunction)psyco_isqlquote_prepare, - METH_VARARGS, psyco_isqlquote_prepare_doc}, + /* {"prepare", (PyCFunction)psyco_isqlquote_prepare, + METH_VARARGS, psyco_isqlquote_prepare_doc}, */ {NULL} }; diff --git a/psycopg/psycopgmodule.c b/psycopg/psycopgmodule.c index 0d9e40d9..da2109d0 100644 --- a/psycopg/psycopgmodule.c +++ b/psycopg/psycopgmodule.c @@ -34,6 +34,7 @@ #include "psycopg/adapter_qstring.h" #include "psycopg/adapter_binary.h" #include "psycopg/adapter_pboolean.h" +#include "psycopg/adapter_asis.h" #ifdef HAVE_MXDATETIME @@ -187,6 +188,10 @@ psyco_adapters_init(PyObject *mod) { PyObject *call; + microprotocols_add(&PyFloat_Type, NULL, (PyObject*)&asisType); + microprotocols_add(&PyInt_Type, NULL, (PyObject*)&asisType); + microprotocols_add(&PyLong_Type, NULL, (PyObject*)&asisType); + microprotocols_add(&PyString_Type, NULL, (PyObject*)&qstringType); microprotocols_add(&PyUnicode_Type, NULL, (PyObject*)&qstringType); microprotocols_add(&PyBuffer_Type, NULL, (PyObject*)&binaryType); @@ -225,6 +230,9 @@ static encodingPair encodings[] = { {"SQL_ASCII", "ascii"}, {"LATIN1", "latin_1"}, {"UNICODE", "utf_8"}, + /* some compatibility stuff */ + {"latin-1", "latin_1"}, + {NULL, NULL} }; static void psyco_encodings_fill(PyObject *dict) @@ -328,7 +336,9 @@ static PyMethodDef psycopgMethods[] = { METH_VARARGS, psyco_register_type_doc}, {"new_type", (PyCFunction)typecast_from_python, METH_VARARGS|METH_KEYWORDS}, - + + {"AsIs", (PyCFunction)psyco_AsIs, + METH_VARARGS, psyco_AsIs_doc}, {"QuotedString", (PyCFunction)psyco_QuotedString, METH_VARARGS, psyco_QuotedString_doc}, {"Boolean", (PyCFunction)psyco_Boolean, @@ -388,8 +398,9 @@ init_psycopg(void) cursorType.ob_type = &PyType_Type; typecastType.ob_type = &PyType_Type; qstringType.ob_type = &PyType_Type; - binaryType.ob_type = &PyType_Type; - isqlquoteType.ob_type = &PyType_Type; + binaryType.ob_type = &PyType_Type; + isqlquoteType.ob_type = &PyType_Type; + asisType.ob_type = &PyType_Type; if (PyType_Ready(&connectionType) == -1) return; if (PyType_Ready(&cursorType) == -1) return; @@ -397,6 +408,7 @@ init_psycopg(void) if (PyType_Ready(&qstringType) == -1) return; if (PyType_Ready(&binaryType) == -1) return; if (PyType_Ready(&isqlquoteType) == -1) return; + if (PyType_Ready(&asisType) == -1) return; #ifdef HAVE_PYBOOL pbooleanType.ob_type = &PyType_Type; diff --git a/setup.py b/setup.py index 32f8fff2..6d771c99 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ from distutils.core import setup, Extension from distutils.sysconfig import get_python_inc import distutils.ccompiler -PSYCOPG_VERSION = '1.99.11' +PSYCOPG_VERSION = '1.99.11/devel' have_pydatetime = False have_mxdatetime = False @@ -114,7 +114,8 @@ sources = [ 'psycopgmodule.c', 'pqpath.c', 'typecast.c', 'microprotocols.c', 'microprotocols_proto.c', 'connection_type.c', 'connection_int.c', 'cursor_type.c', 'cursor_int.c', - 'adapter_qstring.c', 'adapter_pboolean.c', 'adapter_binary.c'] + 'adapter_qstring.c', 'adapter_pboolean.c', 'adapter_binary.c', + 'adapter_asis.c'] # check for mx package mxincludedir = os.path.join(get_python_inc(plat_specific=1), "mx")