Changeset 814 for trunk/pgmodule.c


Ignore:
Timestamp:
Feb 3, 2016, 3:23:20 PM (4 years ago)
Author:
cito
Message:

Add typecasting of dates, times, timestamps, intervals

So far, PyGreSQL has returned these types only as strings (in various
formats depending on the DateStyle? setting) and left it to the user
to parse and interpret the strings. These types are now properly cast
into the corresponding detetime types of Python, and this works with
any setting of DatesStyle?, even if you change DateStyle? in the middle
of a database session.

To implement this, a fast method for getting the datestyle (cached and
without roundtrip to the database) has been added. Also, the typecast
mechanism has been extended so that typecast functions can optionally
also take the connection as argument.

The date and time typecast functions have been implemented in Python
using the new typecast registry and added to both pg and pgdb. Some
duplication of code in the two modules was unavoidable, since we don't
want the modules to be dependent of each other or install additional
helper modules. One day we might want to change this, put everything
in one package and factor out some of the functionality.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/pgmodule.c

    r813 r814  
    9292                                *namedresult = NULL, /* function for getting named results */
    9393                                *jsondecode = NULL; /* function for decoding json strings */
     94static const char *date_format = NULL; /* date format that is always assumed */
    9495static char decimal_point = '.'; /* decimal point used in money values */
    9596static int bool_as_text = 0; /* whether bool shall be returned as text */
     
    140141        PyObject_HEAD
    141142        int                     valid;                          /* validity flag */
    142         PGconn     *cnx;                                /* PostGres connection handle */
     143        PGconn     *cnx;                                /* Postgres connection handle */
     144        const char *date_format;                /* date format derived from datestyle */
    143145        PyObject   *cast_hook;                  /* external typecast method */
    144146        PyObject   *notice_receiver;    /* current notice receiver */
     
    295297                case VARCHAROID:
    296298                case NAMEOID:
    297                 case DATEOID:
    298                 case INTERVALOID:
    299                 case TIMEOID:
    300                 case TIMETZOID:
    301                 case TIMESTAMPOID:
    302                 case TIMESTAMPTZOID:
    303299                case REGTYPEOID:
    304300                        t = PYGRES_TEXT;
     
    353349                case VARCHARARRAYOID:
    354350                case NAMEARRAYOID:
    355                 case DATEARRAYOID:
    356                 case INTERVALARRAYOID:
    357                 case TIMEARRAYOID:
    358                 case TIMETZARRAYOID:
    359                 case TIMESTAMPARRAYOID:
    360                 case TIMESTAMPTZARRAYOID:
    361351                case REGTYPEARRAYOID:
    362352                        t = array_as_text ? PYGRES_TEXT : (PYGRES_TEXT | PYGRES_ARRAY);
     
    20222012        }
    20232013
     2014        /* this may have changed the datestyle, so we reset the date format
     2015           in order to force fetching it newly when next time requested */
     2016        self->date_format = date_format; /* this is normally NULL */
     2017
    20242018        /* checks result status */
    20252019        if ((status = PQresultStatus(result)) != PGRES_TUPLES_OK)
     
    24702464}
    24712465
     2466/* internal function converting a Postgres datestyles to date formats */
     2467static const char *
     2468date_style_to_format(const char *s)
     2469{
     2470        static const char *formats[] = {
     2471                "%Y-%m-%d",             /* 0 = ISO */
     2472                "%m-%d-%Y",             /* 1 = Postgres, MDY */
     2473                "%d-%m-%Y",             /* 2 = Postgres, DMY */
     2474                "%m/%d/%Y",             /* 3 = SQL, MDY */
     2475                "%d/%m/%Y",             /* 4 = SQL, DMY */
     2476                "%d.%m.%Y"};    /* 5 = German */
     2477
     2478        switch (s ? *s : 'I')
     2479        {
     2480                case 'P': /* Postgres */
     2481                        s = strchr(s + 1, ',');
     2482                        if (s) do ++s; while (*s && *s == ' ');
     2483                        return formats[s && *s == 'D' ? 2 : 1];
     2484                case 'S': /* SQL */
     2485                        s = strchr(s + 1, ',');
     2486                        if (s) do ++s; while (*s && *s == ' ');
     2487                        return formats[s && *s == 'D' ? 4 : 3];
     2488                case 'G': /* German */
     2489                        return formats[5];
     2490                default: /* ISO */
     2491                        return formats[0]; /* ISO is the default */
     2492        }
     2493}
     2494
     2495/* internal function converting a date format to a Postgres datestyle */
     2496static const char *
     2497date_format_to_style(const char *s)
     2498{
     2499        static const char *datestyle[] = {
     2500                "ISO, YMD",                     /* 0 = %Y-%m-%d */
     2501                "Postgres, MDY",        /* 1 = %m-%d-%Y */
     2502                "Postgres, DMY",        /* 2 = %d-%m-%Y */
     2503                "SQL, MDY",             /* 3 = %m/%d/%Y */
     2504                "SQL, DMY",             /* 4 = %d/%m/%Y */
     2505                "German, DMY"};         /* 5 = %d.%m.%Y */
     2506
     2507        switch (s ? s[1] : 'Y')
     2508        {
     2509                case 'm':
     2510                        switch (s[2])
     2511                        {
     2512                                case '/':
     2513                                        return datestyle[3]; /* SQL, MDY */
     2514                                default:
     2515                                        return datestyle[1]; /* Postgres, MDY */
     2516                        }
     2517                case 'd':
     2518                        switch (s[2])
     2519                        {
     2520                                case '/':
     2521                                        return datestyle[4]; /* SQL, DMY */
     2522                                case '.':
     2523                                        return datestyle[5]; /* German */
     2524                                default:
     2525                                        return datestyle[2]; /* Postgres, DMY */
     2526                        }
     2527                default:
     2528                        return datestyle[0]; /* ISO */
     2529        }
     2530}
     2531
     2532/* get current date format */
     2533static char connDateFormat__doc__[] =
     2534"date_format() -- return the current date format";
     2535
     2536static PyObject *
     2537connDateFormat(connObject *self, PyObject *noargs)
     2538{
     2539        const char *fmt;
     2540
     2541        if (!self->cnx)
     2542        {
     2543                PyErr_SetString(PyExc_TypeError, "Connection is not valid");
     2544                return NULL;
     2545        }
     2546
     2547        /* check if the date format is cached in the connection */
     2548        fmt = self->date_format;
     2549        if (!fmt)
     2550        {
     2551                fmt = date_style_to_format(PQparameterStatus(self->cnx, "DateStyle"));
     2552                self->date_format = fmt; /* cache the result */
     2553        }
     2554
     2555        return PyStr_FromString(fmt);
     2556}
     2557
    24722558#ifdef ESCAPING_FUNCS
    24732559
     
    30403126        {"parameter", (PyCFunction) connParameter, METH_VARARGS,
    30413127                        connParameter__doc__},
     3128        {"date_format", (PyCFunction) connDateFormat, METH_NOARGS,
     3129                        connDateFormat__doc__},
    30423130
    30433131#ifdef ESCAPING_FUNCS
     
    32963384                return NULL;
    32973385        }
     3386
     3387        /* this may have changed the datestyle, so we reset the date format
     3388           in order to force fetching it newly when next time requested */
     3389        self->pgcnx->date_format = date_format; /* this is normally NULL */
    32983390
    32993391        /* checks result status */
     
    40794171        npgobj->valid = 1;
    40804172        npgobj->cnx = NULL;
     4173        npgobj->date_format = date_format;
    40814174        npgobj->cast_hook = NULL;
    40824175        npgobj->notice_receiver = NULL;
     
    47244817}
    47254818
     4819/* set fixed datestyle */
     4820static char pgSetDatestyle__doc__[] =
     4821"set_datestyle(style) -- set which style is assumed";
     4822
     4823static PyObject *
     4824pgSetDatestyle(PyObject *self, PyObject *args)
     4825{
     4826        const char         *datestyle = NULL;
     4827
     4828        /* gets arguments */
     4829        if (!PyArg_ParseTuple(args, "z", &datestyle))
     4830        {
     4831                PyErr_SetString(PyExc_TypeError,
     4832                        "Function set_datestyle() expects a string or None as argument");
     4833                return NULL;
     4834        }
     4835
     4836        date_format = datestyle ? date_style_to_format(datestyle) : NULL;
     4837
     4838        Py_INCREF(Py_None); return Py_None;
     4839}
     4840
     4841/* get fixed datestyle */
     4842static char pgGetDatestyle__doc__[] =
     4843"get_datestyle() -- get which date style is assumed";
     4844
     4845static PyObject *
     4846pgGetDatestyle(PyObject *self, PyObject *noargs)
     4847{
     4848        if (date_format)
     4849        {
     4850                return PyStr_FromString(date_format_to_style(date_format));
     4851        }
     4852        else
     4853        {
     4854                Py_INCREF(Py_None); return Py_None;
     4855        }
     4856}
     4857
    47264858/* get decimal point */
    47274859static char pgGetDecimalPoint__doc__[] =
     
    47994931
    48004932static PyObject *
    4801 pgSetDecimal(PyObject *self, PyObject *args)
     4933pgSetDecimal(PyObject *self, PyObject *cls)
    48024934{
    48034935        PyObject *ret = NULL;
    4804         PyObject *cls;
    4805 
    4806         if (PyArg_ParseTuple(args, "O", &cls))
    4807         {
    4808                 if (cls == Py_None)
    4809                 {
    4810                         Py_XDECREF(decimal); decimal = NULL;
    4811                         Py_INCREF(Py_None); ret = Py_None;
    4812                 }
    4813                 else if (PyCallable_Check(cls))
    4814                 {
    4815                         Py_XINCREF(cls); Py_XDECREF(decimal); decimal = cls;
    4816                         Py_INCREF(Py_None); ret = Py_None;
    4817                 }
    4818                 else
    4819                         PyErr_SetString(PyExc_TypeError,
    4820                                 "Function set_decimal() expects"
    4821                                  " a callable or None as argument");
    4822         }
     4936
     4937        if (cls == Py_None)
     4938        {
     4939                Py_XDECREF(decimal); decimal = NULL;
     4940                Py_INCREF(Py_None); ret = Py_None;
     4941        }
     4942        else if (PyCallable_Check(cls))
     4943        {
     4944                Py_XINCREF(cls); Py_XDECREF(decimal); decimal = cls;
     4945                Py_INCREF(Py_None); ret = Py_None;
     4946        }
     4947        else
     4948                PyErr_SetString(PyExc_TypeError,
     4949                        "Function set_decimal() expects"
     4950                         " a callable or None as argument");
    48234951
    48244952        return ret;
     
    49595087
    49605088static PyObject *
    4961 pgSetNamedresult(PyObject *self, PyObject *args)
     5089pgSetNamedresult(PyObject *self, PyObject *func)
    49625090{
    49635091        PyObject *ret = NULL;
    4964         PyObject *func;
    4965 
    4966         if (PyArg_ParseTuple(args, "O", &func))
    4967         {
    4968                 if (func == Py_None)
    4969                 {
    4970                         Py_XDECREF(namedresult); namedresult = NULL;
    4971                         Py_INCREF(Py_None); ret = Py_None;
    4972                 }
    4973                 else if (PyCallable_Check(func))
    4974                 {
    4975                         Py_XINCREF(func); Py_XDECREF(namedresult); namedresult = func;
    4976                         Py_INCREF(Py_None); ret = Py_None;
    4977                 }
    4978                 else
    4979                         PyErr_SetString(PyExc_TypeError,
    4980                                 "Function set_namedresult() expectst"
    4981                                  " a callable or None as argument");
    4982         }
     5092
     5093        if (func == Py_None)
     5094        {
     5095                Py_XDECREF(namedresult); namedresult = NULL;
     5096                Py_INCREF(Py_None); ret = Py_None;
     5097        }
     5098        else if (PyCallable_Check(func))
     5099        {
     5100                Py_XINCREF(func); Py_XDECREF(namedresult); namedresult = func;
     5101                Py_INCREF(Py_None); ret = Py_None;
     5102        }
     5103        else
     5104                PyErr_SetString(PyExc_TypeError,
     5105                        "Function set_namedresult() expects"
     5106                         " a callable or None as argument");
    49835107
    49845108        return ret;
     
    50045128/* set json decode function */
    50055129static char pgSetJsondecode__doc__[] =
    5006 "set_jsondecode() -- set a function to be used for decoding json results";
    5007 
    5008 static PyObject *
    5009 pgSetJsondecode(PyObject *self, PyObject *args)
     5130"set_jsondecode(func) -- set a function to be used for decoding json results";
     5131
     5132static PyObject *
     5133pgSetJsondecode(PyObject *self, PyObject *func)
    50105134{
    50115135        PyObject *ret = NULL;
    5012         PyObject *func;
    5013 
    5014         if (PyArg_ParseTuple(args, "O", &func))
    5015         {
    5016                 if (func == Py_None)
    5017                 {
    5018                         Py_XDECREF(jsondecode); jsondecode = NULL;
    5019                         Py_INCREF(Py_None); ret = Py_None;
    5020                 }
    5021                 else if (PyCallable_Check(func))
    5022                 {
    5023                         Py_XINCREF(func); Py_XDECREF(jsondecode); jsondecode = func;
    5024                         Py_INCREF(Py_None); ret = Py_None;
    5025                 }
    5026                 else
    5027                         PyErr_SetString(PyExc_TypeError,
    5028                                 "Function jsondecode() expects"
    5029                                  " a callable or None as argument");
    5030         }
     5136
     5137        if (func == Py_None)
     5138        {
     5139                Py_XDECREF(jsondecode); jsondecode = NULL;
     5140                Py_INCREF(Py_None); ret = Py_None;
     5141        }
     5142        else if (PyCallable_Check(func))
     5143        {
     5144                Py_XINCREF(func); Py_XDECREF(jsondecode); jsondecode = func;
     5145                Py_INCREF(Py_None); ret = Py_None;
     5146        }
     5147        else
     5148                PyErr_SetString(PyExc_TypeError,
     5149                        "Function jsondecode() expects"
     5150                         " a callable or None as argument");
    50315151
    50325152        return ret;
     
    54205540        {"unescape_bytea", (PyCFunction) pgUnescapeBytea, METH_O,
    54215541                        pgUnescapeBytea__doc__},
     5542        {"get_datestyle", (PyCFunction) pgGetDatestyle, METH_NOARGS,
     5543                        pgGetDatestyle__doc__},
     5544        {"set_datestyle", (PyCFunction) pgSetDatestyle, METH_VARARGS,
     5545                        pgSetDatestyle__doc__},
    54225546        {"get_decimal_point", (PyCFunction) pgGetDecimalPoint, METH_NOARGS,
    54235547                        pgGetDecimalPoint__doc__},
     
    54265550        {"get_decimal", (PyCFunction) pgGetDecimal, METH_NOARGS,
    54275551                        pgGetDecimal__doc__},
    5428         {"set_decimal", (PyCFunction) pgSetDecimal, METH_VARARGS,
     5552        {"set_decimal", (PyCFunction) pgSetDecimal, METH_O,
    54295553                        pgSetDecimal__doc__},
    54305554        {"get_bool", (PyCFunction) pgGetBool, METH_NOARGS, pgGetBool__doc__},
     
    54385562        {"get_namedresult", (PyCFunction) pgGetNamedresult, METH_NOARGS,
    54395563                        pgGetNamedresult__doc__},
    5440         {"set_namedresult", (PyCFunction) pgSetNamedresult, METH_VARARGS,
     5564        {"set_namedresult", (PyCFunction) pgSetNamedresult, METH_O,
    54415565                        pgSetNamedresult__doc__},
    54425566        {"get_jsondecode", (PyCFunction) pgGetJsondecode, METH_NOARGS,
    54435567                        pgGetJsondecode__doc__},
    5444         {"set_jsondecode", (PyCFunction) pgSetJsondecode, METH_VARARGS,
     5568        {"set_jsondecode", (PyCFunction) pgSetJsondecode, METH_O,
    54455569                        pgSetJsondecode__doc__},
    54465570        {"cast_array", (PyCFunction) pgCastArray, METH_VARARGS|METH_KEYWORDS,
Note: See TracChangeset for help on using the changeset viewer.