Package Gnumed :: Package pycommon :: Module gmBusinessDBObject |
[frames] | no frames]

# Module gmBusinessDBObject

source code

    GNUmed database object business class.

    Overview
    --------
    This class wraps a source relation (table, view) which
    represents an entity that makes immediate business sense
    such as a vaccination or a medical document. In many if
    not most cases this source relation is a denormalizing
    view. The data in that view will in most cases, however,
    originate from several normalized tables. One instance
    of this class represents one row of said source relation.

    Note, however, that this class does not *always* simply
    wrap a single table or view. It can also encompass several
    relations (views, tables, sequences etc) that taken together
    form an object meaningful to *business* logic.

    Initialization
    --------------
    There are two ways to initialize an instance with values.
    One way is to pass a "primary key equivalent" object into
    __init__(). Refetch_payload() will then pull the data from
    the backend. Another way would be to fetch the data outside
    the instance and pass it in via the <row> argument. In that
    case the instance will not initially connect to the databse
    which may offer a great boost to performance.

    Values API
    ----------
    Field values are cached for later access. They can be accessed
    by a dictionary API, eg:

            old_value = object['field']
            object['field'] = new_value

    The field names correspond to the respective column names
    in the "main" source relation. Accessing non-existant field
    names will raise an error, so does trying to set fields not
    listed in self.__class__._updatable_fields. To actually
    store updated values in the database one must explicitly
    call save_payload().

    The class will in many cases be enhanced by accessors to
    related data that is not directly part of the business
    object itself but are closely related, such as codes
    linked to a clinical narrative entry (eg a diagnosis). Such
    accessors in most cases start with get_*. Related setters
    start with set_*. The values can be accessed via the
    object['field'] syntax, too, but they will be cached
    independantly.

    Concurrency handling
    --------------------
    GNUmed connections always run transactions in isolation level
    "serializable". This prevents transactions happening at the
    *very same time* to overwrite each other's data. All but one
    of them will abort with a concurrency error (eg if a
    transaction runs a select-for-update later than another one
    it will hang until the first transaction ends. Then it will
    succeed or fail depending on what the first transaction
    did). This is standard transactional behaviour.

    However, another transaction may have updated our row
    between the time we first fetched the data and the time we
    start the update transaction. This is noticed by getting the
    XMIN system column for the row when initially fetching the
    data and using that value as a where condition value when
    updating the row later. If the row had been updated (xmin
    changed) or deleted (primary key disappeared) in the
    meantime the update will touch zero rows (as no row with
    both PK and XMIN matching is found) even if the query itself
    syntactically succeeds.

    When detecting a change in a row due to XMIN being different
    one needs to be careful how to represent that to the user.
    The row may simply have changed but it also might have been
    deleted and a completely new and unrelated row which happens
    to have the same primary key might have been created ! This
    row might relate to a totally different context (eg. patient,
    episode, encounter).

    One can offer all the data to the user:

    self.original_payload
    - contains the data at the last successful refetch

    self.modified_payload
    - contains the modified payload just before the last
      failure of save_payload() - IOW what is currently
      in the database

    self._payload
    - contains the currently active payload which may or
      may not contain changes

    For discussion on this see the thread starting at:

            http://archives.postgresql.org/pgsql-general/2004-10/msg01352.php

    and here

            http://groups.google.com/group/pgsql.general/browse_thread/thread/e3566ba76173d0bf/6cf3c243a86d9233
            (google for "XMIN semantic at peril")

    Problem cases with XMIN:

    1) not unlikely
    - a very old row is read with XMIN
    - vacuum comes along and sets XMIN to FrozenTransactionId
      - now XMIN changed but the row actually didn't !
    - an update with "... where xmin = old_xmin ..." fails
      although there is no need to fail

    2) quite unlikely
    - a row is read with XMIN
    - a long time passes
    - the original XMIN gets frozen to FrozenTransactionId
    - another writer comes along and changes the row
    - incidentally the exact same old row gets the old XMIN *again*
      - now XMIN is (again) the same but the data changed !
    - a later update fails to detect the concurrent change !!

    TODO:
    The solution is to use our own column for optimistic locking
    which gets updated by an AFTER UPDATE trigger.

Author: K.Hilbert <Karsten.Hilbert@gmx.net>

License: GPL v2 or later

Classes
| cBusinessDBObject Represents business objects in the database.

Functions
jsonclasshintify(obj) turn the data into a list of dicts, adding "class hints". | source code

Variables
| __package__ = ``'``Gnumed.pycommon``'``

Imports: sys, types, inspect, logging, datetime, gmExceptions, gmPG2, pydt_strftime, tex_escape_string, xetex_escape_string

Function Details

### jsonclasshintify(obj) | source code
turn the data into a list of dicts, adding "class hints". all objects get turned into dictionaries which the other end will interpret as "object", via the __jsonclass__ hint, as specified by the JSONRPC protocol standard.