Re: How to force a different datetime output?

Skip Montanaro <[email protected]> Fri, 28 May 2004 08:59:57 -0500
Newsgroups gmane.comp.python.sybase
Message-ID <[email protected]>
    >> I'm trying to get the Object Craft Sybase module used at work instead
    >> of a homegrown hack.  Based upon a lot of use of the homegrown
    >> module, one requirement is that dates be represented as floating
    >> point seconds since the Unix epoch.  It appears that Sybase returns
    >> some sort of DateTime object....

    >> Can I cleanly get from a DateTime object to a float or do I have to
    >> inject a new version of _column_value() into Sybase.py?  ('Twould be
    >> nice if there was a standard way to establish type converters on a
    >> per-connection basis.)

    ...

    >>  Error: Layer: 2, Origin: 1
    >>  cs_convert: cslib user api layer: external error: Conversion between 12 and 10 datatypes is not supported.

    Dave> That is strange.  Looking into the Sybase include files this seems
    Dave> to be saying that it will not convert from CS_DATETIME_TYPE to
    Dave> CS_FLOAT_TYPE.  I am fairly sure that this should work.  What
    Dave> version of the Sybase client libraries are you using?

We're using 12.5 I believe.  At least SYBASE refers to 12.5:

>>> os.environ.get("SYBASE")
'/opt/sybase-12.5'

Our current environment is a bit weird though.  We're running mostly Solaris
on Intel.  The most recent update from Sybase included libraries but no
include files.  (Sybase indicated this was intentional - apparently you're
not supposed to be able to write clients which run on Solaris/Intel!)  One
of the admins copied the include files from the SPARC platform.  There were
mismatches between the features the include files announced and those the
.so files actually implemented, so I wound up writing a Makefile based upon
the output of the distutils build and started stripping off -DHAVE_... flags
until it would compile, link and import without failure.  I'm left with
these CPPFLAGS:

    CPPFLAGS = -fno-strict-aliasing -DNDEBUG -DHAVE_CT_CURSOR \
               -DHAVE_CT_DATA_INFO -DHAVE_CT_DYNAMIC -DHAVE_CT_SEND_DATA \
               -DHAVE_CT_SETPARAM -DHAVE_CS_CALC -DHAVE_CS_CMP \
               -I$(SYBDIR)/include -I$(PYTHONDIR)/include/python2.3

Would any of those -DHAVE... options be related to the error I saw?
(Thankfully we are moving toward Linux, so will hopefully get marginally
better support from Sybase in the future.)

At any rate, I took a first stab yesterday afternoon at adding type
converters to Sybase.py.  The attached context diff against 0.36 is the
current state.  It adds an optional converters argument to the Connection()
call.  If given, it should be a mapping object of some sort which maps a
given Sybase type to a converter function.  I'm hopeful the only slightly
controversial aspect is my inclusion of the standard library's datetime type
at the head of the list of possible date/time types and that the notion of
type converters per se is uncontroversial.  In our environment I'll simply
initalize Connection objects in a wrapper library with a converter like
this:

    >>> import Sybase
    >>> import time
    >>> def dt_as_float(dt):
    ...   return time.mktime(Sybase._convert_datetime(dt).timetuple())
    ... 
    >>> db = Sybase.Connection(..., converters={
    ...               Sybase.DateTimeType: dt_as_float})
    >>> c = db.cursor()
    >>> c.execute("""select date from ... where ...""")
    >>> c.fetchone()
    (919317600.0,)

Skip
Sybase.py.diff (application/octet-stream, 7.2 KB)
*** Sybase.py.orig	Thu May 27 15:11:37 2004
--- Sybase.py	Fri May 28 08:24:42 2004
***************
*** 4,19 ****
  # LICENCE - see LICENCE file distributed with this software for details.
  #
  
- try:
-     import DateTime
-     use_datetime = 1
- except ImportError:
-     try:
-         import mx.DateTime
-         DateTime = mx.DateTime
-         use_datetime = 1
-     except ImportError:
-         use_datetime = 0
  import sys
  import time
  import string
--- 4,9 ----
***************
*** 183,207 ****
          bufs.append(buf)
      return bufs
  
! def _column_value(val):
!     if use_datetime and type(val) is DateTimeType:
!         return DateTime.DateTime(val.year, val.month + 1, val.day,
!                                  val.hour, val.minute,
!                                  val.second + val.msecond / 1000.0)
!     else:
!         return val
  
! def _extract_row(bufs, n):
      '''Extract a row tuple from buffers.
      '''
      row = [None] * len(bufs)
      col = 0
      for buf in bufs:
!         row[col] = _column_value(buf[n])
          col = col + 1
      return tuple(row)
  
! def _fetch_rows(cmd, bufs, rows):
      '''Fetch rows into bufs.
  
      When bound to buffers for a single row, return a row tuple.
--- 173,232 ----
          bufs.append(buf)
      return bufs
  
! def _convert_datetime(val):
!     if DT is not None:
!         return DT(val.year, val.month + 1, val.day,
!                   val.hour, val.minute,
!                   val.second, val.msecond * 1000)
!     return val
  
! def _convert_DateTime(val):
!     if DT is not None:
!         return DT(val.year, val.month + 1, val.day,
!                   val.hour, val.minute,
!                   val.second + val.msecond / 1000.0)
!     return val
! 
! def _convert_MxDateTime(val):
!     if DT is not None:
!         return DT(val.year, val.month + 1, val.day,
!                   val.hour, val.minute,
!                   val.second + val.msecond / 1000.0)
!     return val
! 
! try:
!     import datetime
!     DT = datetime.datetime
!     _cvt_dt = _convert_datetime
! except ImportError:
!     try:
!         import DateTime
!         DT = DateTime.DateTime
!         _cvt_dt = _convert_DateTime
!     except ImportError:
!         try:
!             import mx.DateTime
!             DT = mx.DateTime.DateTime
!             _cvt_dt = convert_MxDateTime
!         except ImportError:
!             DT = None
! 
! 
! def _extract_row(bufs, n, converters=None):
      '''Extract a row tuple from buffers.
      '''
      row = [None] * len(bufs)
      col = 0
      for buf in bufs:
!         v = buf[n]
!         t = type(v)
!         row[col] = (t in converters.keys()
!                       and converters[t](v)
!                       or _column_value(v))
          col = col + 1
      return tuple(row)
  
! def _fetch_rows(cmd, bufs, rows, converters=None):
      '''Fetch rows into bufs.
  
      When bound to buffers for a single row, return a row tuple.
***************
*** 218,227 ****
          raise Error('ct_fetch')
      if bufs[0].count > 1:
          for i in xrange(rows_read):
!             rows.append(_extract_row(bufs, i))
          return rows_read
      else:
!         rows.append(_extract_row(bufs, 0))
          return 1
  
  def _bufs_description(bufs):
--- 243,252 ----
          raise Error('ct_fetch')
      if bufs[0].count > 1:
          for i in xrange(rows_read):
!             rows.append(_extract_row(bufs, i, converters))
          return rows_read
      else:
!         rows.append(_extract_row(bufs, 0, converters))
          return 1
  
  def _bufs_description(bufs):
***************
*** 291,297 ****
          bufs = _row_bind(self._cmd, self._arraysize)
          self._description_list.append(_bufs_description(bufs))
          logical_result = []
!         while _fetch_rows(self._cmd, bufs, logical_result):
              pass
          self._result_list.append(logical_result)
  
--- 316,323 ----
          bufs = _row_bind(self._cmd, self._arraysize)
          self._description_list.append(_bufs_description(bufs))
          logical_result = []
!         while _fetch_rows(self._cmd, bufs, logical_result,
!                           self._owner.converters()):
              pass
          self._result_list.append(logical_result)
  
***************
*** 372,377 ****
--- 398,404 ----
          self._lock_count = 0
          self._state = _LAZY_IDLE
          self._open()
+         self._converters = self._owner.converters()
  
      def _set_state(self, state):
          _ctx.debug_msg('_set_state: %s\n' % _state_names[state])
***************
*** 454,460 ****
                      try:
                          self._array_pos = 0
                          self._array = []
!                         _fetch_rows(self._cmd, self._bufs, self._array)
                      except Error:
                          status = self._cmd.ct_cancel(CS_CANCEL_ALL)
                          if status == CS_SUCCEED:
--- 481,488 ----
                      try:
                          self._array_pos = 0
                          self._array = []
!                         _fetch_rows(self._cmd, self._bufs, self._array,
!                                     self._converters)
                      except Error:
                          status = self._cmd.ct_cancel(CS_CANCEL_ALL)
                          if status == CS_SUCCEED:
***************
*** 493,499 ****
                  else:
                      try:
                          rows = []
!                         _fetch_rows(self._cmd, self._bufs, rows)
                      except Error:
                          status = self._cmd.ct_cancel(CS_CANCEL_ALL)
                          if status == CS_SUCCEED:
--- 521,528 ----
                  else:
                      try:
                          rows = []
!                         _fetch_rows(self._cmd, self._bufs, rows,
!                                     self._converters)
                      except Error:
                          status = self._cmd.ct_cancel(CS_CANCEL_ALL)
                          if status == CS_SUCCEED:
***************
*** 758,764 ****
  
  class Connection:
      def __init__(self, dsn, user, passwd, database = None,
!                  strip = 0, auto_commit = 0, delay_connect = 0, locking = 1):
          '''DB-API Sybase.Connect()
          '''
          self._conn = self._cmd = None
--- 787,794 ----
  
  class Connection:
      def __init__(self, dsn, user, passwd, database = None,
!                  strip = 0, auto_commit = 0, delay_connect = 0, locking = 1,
!                  converters = None):
          '''DB-API Sybase.Connect()
          '''
          self._conn = self._cmd = None
***************
*** 770,775 ****
--- 800,808 ----
          self._do_locking = locking
          self._is_connected = 0
          self.arraysize = 32
+         self._converters = converters or {
+             DateTimeType: _cvt_dt
+             }
          if locking:
              self._connlock = threading.RLock()
  
***************
*** 909,914 ****
--- 942,950 ----
          finally:
              self._unlock()
  
+     def converters(self):
+         return self._converters
+ 
  def connect(dsn, user, passwd, database = None,
              strip = 0, auto_commit = 0, delay_connect = 0, locking = 1):
      return Connection(dsn, user, passwd, database,