Contact: fumanchu@aminus.org

Log in as guest/dejavu to create tickets

I think I've seen this ORM somewhere before...

Changeset 25

Show
Ignore:
Timestamp:
11/03/04 00:18:32
Author:
fumanchu
Message:

1. Moved UnitCollection?._IDs to a UnitProperty?, .Members
2. Bug in CachingProxy? save (not storing dirty unit data)
3. Generalized the storage strategy of UnitCollection?.ID (use new table instead of a field) to any property, configurable by deployer.
4. Stuffed common try/except into SM.recordset, execute.
5. Reworked column types (createCoercion) to FieldTypeAdapters?.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/engines.py

    r24 r25  
    4141    """ 
    4242     
    43     _IDs = None 
     43    Members = dejavu.UnitProperty(u'Members', list) 
    4444    EngineID = dejavu.UnitProperty(u'EngineID', int, index=True) 
    4545    Type = dejavu.UnitProperty(u'Type') 
     
    4949    def __init__(self, **kwargs): 
    5050        dejavu.Unit.__init__(self) 
    51         self._IDs = sets.Set() 
     51        self.Members = [] 
    5252        self._mutex = threading.RLock() 
    5353         
     
    5656     
    5757    def __getstate__(self): 
    58         return (self._properties, self.dirty, self._IDs
     58        return (self._properties, self.dirty
    5959     
    6060    def __setstate__(self, state): 
    6161        self.sandbox = None 
    6262        self._mutex = threading.RLock() 
    63         self._properties, self.dirty, self._IDs = state 
     63        self._properties, self.dirty = state 
    6464     
    6565    def acquire(self): 
     
    7070     
    7171    def __len__(self): 
    72         return len(self._IDs) 
     72        return len(self.Members) 
    7373     
    7474    def add(self, ID): 
    7575        self.acquire() 
    7676        try: 
    77             self._IDs.add(ID) 
     77            if ID not in self.Members: 
     78                self.Members.append(ID) 
    7879        finally: 
    7980            self.release() 
     
    8586        self.acquire() 
    8687        try: 
    87             return self._IDs.copy() 
     88            return self.Members.copy() 
    8889        finally: 
    8990            self.release() 
     
    9495        self.acquire() 
    9596        try: 
    96             for i, eachID in enumerate(self._IDs): 
     97            for i, eachID in enumerate(self.Members): 
    9798                if quota and i >= quota: 
    9899                    break 
     
    118119    def __copy__(self): 
    119120        newUnit = dejavu.Unit.__copy__(self) 
    120         newUnit._IDs = self._IDs.copy() 
     121        newUnit.Members = self.Members.copy() 
    121122        return newUnit 
    122123     
     
    416417        B.acquire() 
    417418        try: 
    418             B._IDs = A._IDs.copy() 
     419            B.Members = A.Members.copy() 
    419420        finally: 
    420421            A.release() 
     
    434435            A.acquire() 
    435436            try: 
    436                 for unit in self.sandbox.recall(self.arena.class_by_name(A.Type)): 
    437                     A._IDs.add(unit.ID) 
     437                mem = A.Members 
     438                cls = self.arena.class_by_name(A.Type) 
     439                for unit in self.sandbox.recall(cls): 
     440                    id = unit.ID 
     441                    if id not in mem: 
     442                        mem.append(id) 
    438443            finally: 
    439444                A.release() 
     
    446451        B.acquire() 
    447452        try: 
    448             A._IDs = A._IDs.difference(B._IDs) 
     453            A.Members = [x for x in A.Members if x not in B.Members] 
    449454        finally: 
    450455            A.release() 
     
    460465            try: 
    461466                cls = self.arena.class_by_name(A.Type) 
     467                mem = A.Members 
    462468                for unit in self.sandbox.recall(cls, expr): 
    463                     A._IDs.add(unit.ID) 
     469                    id = unit.ID 
     470                    if id not in mem: 
     471                        mem.append(unit.ID) 
    464472            finally: 
    465473                A.release() 
     
    468476            try: 
    469477                cls = self.arena.class_by_name(A.Type) 
    470                 newset = sets.Set() 
    471                 for id in A._IDs: 
     478                newset = [] 
     479                for id in A.Members: 
    472480                    unit = self.sandbox.unit(cls, ID=id) 
    473481                    if unit and expr.evaluate(unit): 
    474                         newset.add(id) 
    475                 A._IDs = newset 
     482                        newset.append(id) 
     483                A.Members = newset 
    476484            finally: 
    477485                A.release() 
     
    494502        B.acquire() 
    495503        try: 
    496             A._IDs = A._IDs.intersection(B._IDs) 
     504            A.Members = [x for x in A.Members if x in B.Members] 
    497505        finally: 
    498506            A.release() 
     
    521529                oppfunc = getattr(start, eachType.__name__) 
    522530                cls = self.arena.class_by_name(A.Type) 
    523                 newset = sets.Set() 
    524                 for id in A._IDs: 
     531                newset = [] 
     532                for id in A.Members: 
    525533                    unit = self.sandbox.unit(cls, ID=id) 
    526534                    if unit: 
    527535                        for farUnit in oppfunc(unit): 
    528                             newset.add(farUnit.ID) 
    529                 A._IDs = newset 
     536                            farid = farUnit.ID 
     537                            if farid not in newset: 
     538                                newset.append(farid) 
     539                A.Members = newset 
    530540                start = eachType 
    531541                A.Type = eachType.__name__ 
     
    540550        B.acquire() 
    541551        try: 
    542             A._IDs = A._IDs.union(B._IDs) 
     552            amem = A.Members 
     553            for id in B.Members: 
     554                if id not in amem: 
     555                    amem.append(id) 
    543556        finally: 
    544557            A.release() 
  • trunk/storage/__init__.py

    r24 r25  
    158158    def save(self, unit, forceSave=False): 
    159159        """Store the unit.""" 
    160         # Defer saves to sweep(). 
    161         pass 
     160        # Defer store.save() for sweepers. 
     161        if unit.dirty: 
     162            lock = self._get_lock(unit.__class__) 
     163            try: 
     164                cache = self._caches[unit.__class__] 
     165                cache[unit.ID] = pickle.dumps(unit) 
     166            finally: 
     167                lock.release() 
    162168     
    163169    def destroy(self, unit): 
  • trunk/storage/storeado.py

    r24 r25  
    7171        if value is None: 
    7272            return None 
    73         else: 
    74             # Coerce to str for pickle.loads restriction. 
    75             value = str(value) 
     73         
     74        # Coerce to str for pickle.loads restriction. 
     75        value = str(value) 
    7676        return pickle.loads(value) 
    7777     
     
    480480        self.sql, self.imperfect = store.select(unitClass, expr) 
    481481     
    482     def field(self, name, row): 
     482    def field(self, key, row): 
    483483        try: 
    484             col = self.colIndices[name
     484            col = self.colIndices[key
    485485        except KeyError, x: 
    486             x.args += (name, self.unitClass.__name__) 
     486            x.args += (key, self.unitClass.__name__) 
    487487            raise x 
    488         else: 
    489             return (self.fieldTypes[col], self.data[col][row]) 
     488         
     489        return (self.fieldTypes[col], self.data[col][row]) 
    490490     
    491491    def load_data(self): 
    492         try: 
    493             anRS = self.store.recordset(self.sql, adOpenForwardOnly, 
    494                                         adLockReadOnly) 
    495         except pywintypes.com_error, x: 
    496             x.args += (self.sql, ) 
    497             raise x 
     492        anRS = self.store.recordset(self.sql, adOpenForwardOnly, 
     493                                    adLockReadOnly) 
    498494         
    499495        for col, x in enumerate(anRS.Fields): 
     
    511507     
    512508    def units(self): 
     509        s = self.store 
     510        clsname = self.unitClass.__name__ 
     511        tbl = "%s_%s" % (s.prefix, safe_name(clsname)) 
    513512        self.load_data() 
    514513        if len(self.data) > 0: 
     
    517516                coercer = AdapterFromADO(unit) 
    518517                for key in unit.__class__.properties(): 
    519                     value = self.field(key, row) 
    520                     coercer.consume(key, value) 
     518                    if (clsname, key) in s.expanded_columns: 
     519                        # Grab the expanded data 
     520                        try: 
     521                            rs = s.recordset(u"SELECT EXPVAL FROM [%s_%s_%s]" 
     522                                             % (tbl, 
     523                                                safe_name(self.field('ID', row)[1]), 
     524                                                safe_name(key))) 
     525                        except pywintypes.com_error, x: 
     526                            # This usually occurs because the parent Unit 
     527                            # was reserved but no table yet made for these 
     528                            # expanded values. This is OK. TODO: trap this 
     529                            # more specifically by examining the errmsg. 
     530                            values = [] 
     531                        else: 
     532                            values = [pickle.loads(str(x)) for x in rs.GetRows()[0]] 
     533                            rs.Close() 
     534                        expectedType = unit.__class__.property_type(key) 
     535                        values = expectedType(values) 
     536                        # Set the attribute directly to avoid __set__ overhead. 
     537                        unit._properties[key] = values 
     538                    else: 
     539                        value = self.field(key, row) 
     540                        coercer.consume(key, value) 
    521541                # If our SQL is imperfect, don't yield it to the 
    522542                # caller unless it passes evaluate(). 
    523543                if (not self.imperfect) or self.expr.evaluate(unit): 
    524                     yield unit 
    525  
    526  
    527 class CollectionLoaderADO(StoreIteratorADO): 
    528     """Iterable Factory for populating UnitCollections from storage.""" 
    529      
    530     def units(self): 
    531         self.load_data() 
    532         if len(self.data) > 0: 
    533             coll_coercer = AdapterFromADO().coerce 
    534             for row in range(len(self.data[0])): 
    535                 unit = self.unitClass() 
    536                 coercer = AdapterFromADO(unit) 
    537                 for key in unit.__class__.properties(): 
    538                     value = self.field(key, row) 
    539                     coercer.consume(key, value) 
    540                 # If our SQL is imperfect, don't yield it to the 
    541                 # caller unless it passes evaluate(). 
    542                 if (not self.imperfect) or self.expr.evaluate(unit): 
    543                     # Load the collection. 
    544                     # Grab the data dictionary (list of Unit ID's) 
    545                     rsource = (u"SELECT ID FROM [%s__%s]" % 
    546                                (self.store.prefix, safe_name(unit.ID))) 
    547                     try: 
    548                         dataRS = self.store.recordset(rsource) 
    549                     except pywintypes.com_error, x: 
    550                         # This usually occurs because the UnitCollection was 
    551                         # reserved but no table yet made for IDs. This is OK. 
    552                         pass 
    553                     else: 
    554                         idtype = self.store.arena.class_by_name(unit.Type).ID.type 
    555                         while not dataRS.EOF: 
    556                             ID = coll_coercer((0, dataRS.Fields.Item(u'ID')), 
    557                                               idtype) 
    558                             unit.add(ID) 
    559                             dataRS.MoveNext() 
    560                         dataRS.Close() 
    561544                    yield unit 
    562545 
     
    590573     
    591574    def load_data(self): 
    592         try: 
    593             anRS = self.store.recordset(self.sql, adOpenForwardOnly, 
    594                                         adLockReadOnly) 
    595         except pywintypes.com_error, x: 
    596             x.args += (self.sql, ) 
    597             raise x 
     575        anRS = self.store.recordset(self.sql, adOpenForwardOnly, 
     576                                    adLockReadOnly) 
    598577         
    599578        for col, x in enumerate(anRS.Fields): 
     
    628607 
    629608 
     609class FieldTypeAdapter(object): 
     610    """Return the SQL typename of a DB column.""" 
     611     
     612    def coerce(self, cls, key): 
     613        """coerce(cls, key) -> SQL typename for valuetype.""" 
     614        valuetype = cls.property_type(key) 
     615        mod = valuetype.__module__ 
     616        if mod == "__builtin__": 
     617            xform = "coerce_%s" % valuetype.__name__ 
     618        else: 
     619            xform = "coerce_%s_%s" % (mod, valuetype.__name__) 
     620        xform = xform.replace(".", "_") 
     621        try: 
     622            xform = getattr(self, xform) 
     623        except AttributeError: 
     624            raise TypeError("'%s' is not handled by %s." % 
     625                            (valuetype, self.__class__)) 
     626        return xform(cls, key) 
     627     
     628    def _create_str_storage(self, cls, key): 
     629        """This basic string handler does not know anything about the size 
     630        limitations of the particular database. You should use one of the 
     631        subclasses for your particular database if you need storage for 
     632        strings over 255 characters.""" 
     633        prop = getattr(cls, key) 
     634        size = prop.hints.get(u'Size', '255') 
     635        return u"VARCHAR(%s)" % size 
     636     
     637    def coerce_bool(self, cls, key): return u"BIT" 
     638     
     639    def coerce_datetime_datetime(self, cls, key): return u"TIMESTAMP" 
     640    def coerce_datetime_date(self, cls, key): return u"DATE" 
     641    def coerce_datetime_time(self, cls, key): return u"TIME" 
     642     
     643    coerce_dict = _create_str_storage 
     644     
     645    def coerce_fixedpoint_FixedPoint(self, cls, key): return u"FLOAT" 
     646    def coerce_float(self, cls, key): return u"FLOAT" 
     647    def coerce_int(self, cls, key): return u"INTEGER" 
     648     
     649    coerce_list = _create_str_storage 
     650    coerce_str = _create_str_storage 
     651    coerce_tuple = _create_str_storage 
     652    coerce_unicode = _create_str_storage 
     653 
     654 
    630655class StorageManagerADO(storage.StorageManager): 
    631656    """StoreManager to save and retrieve Units via ADO 2.7. 
     
    635660     
    636661    decompiler = ADOSQLDecompiler 
     662    createAdapter = FieldTypeAdapter() 
    637663    threaded = False 
    638664     
     
    653679        self.cursorType = int(allOptions.get(u'CursorType', adOpenDynamic)) 
    654680        self.lockType = int(allOptions.get(u'LockType', adLockOptimistic)) 
     681         
     682        ec = [] 
     683        for prop in allOptions.get(u'Expanded Columns', '').split(","): 
     684            if prop: 
     685                lastdot = prop.rfind(".") 
     686                clsname, key = prop[:lastdot], prop[lastdot + 1:] 
     687                ec.append((clsname, key)) 
     688        self.expanded_columns = ec 
    655689         
    656690        self.reserve_lock = threading.Lock() 
     
    682716            anRS.Open(aQuery, self.connection(), cursorType, lockType) 
    683717        except pywintypes.com_error, x: 
     718            try: 
     719                anRS.Close() 
     720            except: 
     721                pass 
    684722            x.args += (aQuery, ) 
    685723            raise x 
     
    695733        cls = spath[0] 
    696734        leftkey, rightkey = firstcls._associations[cls] 
    697         params = {u'prefix': u'djv'
     735        params = {u'prefix': self.prefix
    698736                  u'left': firstcls.__name__, 
    699737                  u'right': cls.__name__, 
     
    755793        return self.decompiler(self, cls, expr).code() 
    756794     
    757     def execute(self, aQuery): 
    758         self.connection().Execute(aQuery) 
     795    def execute(self, aQuery, conn=None): 
     796        if conn is None: 
     797            conn = self.connection() 
     798        try: 
     799            conn.Execute(aQuery) 
     800        except pywintypes.com_error, x: 
     801            x.args += (aQuery, ) 
     802            raise x 
    759803     
    760804    def recall(self, cls, expr=None, pairs=None): 
    761805        if expr is None: 
    762806            expr = logic.Expression(lambda x: True) 
     807         
    763808        if pairs is not None: 
    764809            return StoreMultiIteratorADO(self, cls, expr, pairs).units() 
    765810        else: 
    766             if cls.__name__ == u'UnitCollection': 
    767                 aLoader = CollectionLoaderADO 
    768             else: 
    769                 aLoader = StoreIteratorADO 
    770             return aLoader(self, cls, expr).units() 
     811            return StoreIteratorADO(self, cls, expr).units() 
    771812     
    772813    def reserve(self, unit): 
     
    780821                                      (self.prefix, safe_name(clsname))) 
    781822                if not (anRS.BOF and anRS.EOF): 
    782 ##                    anRS.MoveFirst() 
    783 ##                    if not (anRS.BOF or anRS.EOF): 
    784823                    data = anRS.GetRows()[0] 
    785824                unit.ID = unit.sequencer.next(data) 
     
    801840        if unit.dirty or forceSave: 
    802841            cls = unit.__class__ 
     842            clsname = cls.__name__ 
    803843            # Use a cursor always--makes mixed-quotes, newline, etc easier. 
    804844            anRS = self.recordset("SELECT * FROM [%s%s] WHERE ID = %s" % 
    805                                   (self.prefix, safe_name(cls.__name__), 
     845                                  (self.prefix, safe_name(clsname), 
    806846                                   AdapterToADOSQL().coerce(unit.ID))) 
    807847            if anRS.EOF and anRS.BOF: 
     
    810850            fmt = AdapterToADOFields() 
    811851            for key in cls.properties(): 
    812                 eachType = cls.property_type(key) 
    813                 newValue = fmt.coerce(getattr(unit, key), eachType) 
    814                 try: 
    815                     anRS.Fields(key).Value = newValue 
    816                 except pywintypes.com_error, x: 
     852                if (clsname, key) in self.expanded_columns: 
     853                    # Special-case this field into its own table. 
     854                    self.save_expanded(unit, key) 
     855                else: 
     856                    eachType = cls.property_type(key) 
     857                    newValue = fmt.coerce(getattr(unit, key), eachType) 
    817858                    try: 
    818                         anRS.Close() 
    819                     except: 
    820                         pass 
    821                     x.args += (cls.__name__, key, eachType, newValue) 
    822                     raise x 
     859                        anRS.Fields(key).Value = newValue 
     860                    except pywintypes.com_error, x: 
     861                        try: 
     862                            anRS.Close() 
     863                        except: 
     864                            pass 
     865                        x.args += (clsname, key, eachType, newValue) 
     866                        raise x 
    823867            anRS.Update() 
    824             # Need to explicitly close here, or save_collection 
    825             # will fail on BeginTrans. 
    826868            anRS.Close() 
    827             if cls.__name__ == u'UnitCollection': 
    828                 self.save_collection(unit) 
    829869            unit.dirty = False 
    830870     
    831     def save_collection(self, unitColl): 
    832         """Update the database from the UnitCollection's data.""" 
     871    def save_expanded(self, unit, key): 
     872        """Save a field using a table specifically for that purpose.""" 
     873        unitcls = unit.__class__ 
     874        table = ("%s_%s_%s_%s" % (self.prefix, safe_name(unitcls.__name__), 
     875                                  safe_name(unit.ID), safe_name(key))) 
     876         
    833877        conn = self.connection() 
    834 ##        # Dropped the begintrans; we were running into limits in MS Access. 
    835 ##        conn.BeginTrans() 
    836         deleteStatement = (u"DROP TABLE [%s__%s];" % 
    837                            (self.prefix, safe_name(unitColl.ID))) 
    838         conn.Execute(deleteStatement) 
    839          
    840         cls = unitColl.unit_class() 
    841         fieldtype = self.createCoercions[cls.ID.type](cls, 'ID') 
    842         createStatement = (u"CREATE TABLE [%s__%s] (ID %s);" 
    843                             % (self.prefix, safe_name(unitColl.ID), 
    844                                fieldtype)) 
    845         conn.Execute(createStatement) 
    846          
    847         ins = u"INSERT INTO [%s__%s] (ID) VALUES (%s);" 
    848         coercer = AdapterToADOSQL().coerce 
    849         for eachID in unitColl.ids(): 
     878        try: 
     879            self.execute((u"DROP TABLE [%s];" % table), conn) 
     880        except pywintypes.com_error, x: 
     881            pass 
     882         
     883        # Ugly, ugly hack to get NTEXT or MEMO as appropriate. The point 
     884        # is, we want a large text field so we can pickle each item. 
     885        ftype = self.createAdapter.coerce_list(None, None) 
     886        self.execute(u"CREATE TABLE [%s] (EXPVAL %s);" % (table, ftype), conn) 
     887         
     888        ins = u"INSERT INTO [" + table + "] (EXPVAL) VALUES ('%s');" 
     889        for v in getattr(unit, key): 
    850890            # Create a row for the unit. 
    851891            # Use an INSERT command (not a cursor) for better performance. 
    852             # TODO: cluster inserts. 
    853             insStatement = ins % (self.prefix, safe_name(unitColl.ID), 
    854                                   coercer(eachID)) 
    855             try: 
    856                 conn.Execute(insStatement) 
    857             except pywintypes.com_error, x: 
    858                 x.args += (insStatement, createStatement) 
    859                 raise x 
    860 ##        conn.CommitTrans() 
     892            v = pickle.dumps(v).replace("'", "''") 
     893            self.execute(ins % v, conn) 
    861894     
    862895    def destroy(self, unit): 
     
    866899                           (self.prefix, safe_name(unit.__class__.__name__), 
    867900                            AdapterToADOSQL().coerce(unit.ID))) 
    868         try: 
    869             self.execute(deleteStatement) 
    870         except pywintypes.com_error, x: 
    871             x.args += (deleteStatement, ) 
    872             raise x 
    873      
    874     def _create_str_storage(unitClass, key): 
    875         """This basic string handler does not know anything about the size 
    876         limitations of the particular database. You should use one of the 
    877         subclasses for your particular database if you need storage for 
    878         strings over 255 characters.""" 
    879         # 'self' is missing from the func sig ON PURPOSE. 
    880         try: 
    881             prop = getattr(unitClass, key) 
    882             size = prop.hints[u'Size'] 
    883             return u"VARCHAR(%s)" % size 
    884         except KeyError: 
    885             return u"VARCHAR(255)" 
    886      
    887     createCoercions = {datetime.datetime: lambda x, y: u"TIMESTAMP", 
    888                        datetime.date: lambda x, y: u"DATE", 
    889                        datetime.time: lambda x, y: u"TIME", 
    890                        str: _create_str_storage, 
    891                        unicode: _create_str_storage, 
    892                        dict: _create_str_storage, 
    893                        list: _create_str_storage, 
    894                        fixedpoint.FixedPoint: lambda x, y: u"FLOAT", 
    895                        int: lambda x, y: u"INTEGER", 
    896                        bool: lambda x, y: u"BIT", 
    897                        float: lambda x, y: u"FLOAT", 
    898                        } 
     901        self.execute(deleteStatement) 
    899902     
    900903    def create_storage(self, unitClass): 
     904        clsname = safe_name(unitClass.__name__) 
     905         
     906        coerce = self.createAdapter.coerce 
    901907        fields = [] 
    902908        for key in unitClass.properties(): 
    903             eachType = unitClass.property_type(key) 
    904             aType = self.createCoercions[eachType](unitClass, key) 
    905             fields.append(u"[%s] %s" % (key, aType)) 
    906         indices = [x + " ASC" for x in unitClass.indices()] 
    907          
    908         createStatement = (u"CREATE TABLE [%s%s] (%s)" % 
    909                            (self.prefix, 
    910                             safe_name(unitClass.__name__), 
    911                             ", ".join(fields))) 
    912         try: 
    913             self.execute(createStatement) 
    914         except Exception, x: 
    915             x.args += (createStatement, ) 
    916             raise x 
    917          
    918         for index in indices: 
    919             indexStatement = (u"CREATE INDEX [%si%s%s] ON [%s%s] (%s)" 
    920                               % (self.prefix, safe_name(unitClass.__name__), 
    921                                  safe_name(index), 
    922                                  self.prefix, safe_name(unitClass.__name__), 
    923                                  index)) 
    924             try: 
    925                 self.execute(indexStatement) 
    926             except Exception, x: 
    927                 x.args += (indexStatement, ) 
    928                 raise x 
    929          
    930         return True 
     909            if (unitClass.__name__, key) not in self.expanded_columns: 
     910                fields.append(u"[%s] %s" % (key, coerce(unitClass, key))) 
     911        self.execute(u"CREATE TABLE [%s%s] (%s)" % 
     912                     (self.prefix, clsname, ", ".join(fields))) 
     913         
     914        for index in unitClass.indices(): 
     915            self.execute(u"CREATE INDEX [%si%s%s] ON [%s%s] (%s ASC)" 
     916                         % (self.prefix, clsname, safe_name(index), 
     917                            self.prefix, clsname, index)) 
    931918     
    932919    def distinct(self, cls, fields, expr=None): 
     
    945932##                             u"distinct()", cls, fields, expr) 
    946933         
    947         try: 
    948             anRS = self.recordset(sql, adOpenForwardOnly, adLockReadOnly) 
    949         except pywintypes.com_error, x: 
    950             x.args += (self.sql, ) 
    951             raise x 
     934        anRS = self.recordset(sql, adOpenForwardOnly, adLockReadOnly) 
    952935         
    953936        fieldTypes = [x.Type for x in anRS.Fields] 
     
    972955 
    973956 
     957########################################################################### 
     958##                                                                       ## 
     959##                             SQL Server                                ## 
     960##                                                                       ## 
     961########################################################################### 
     962 
     963 
     964class FieldTypeAdapter_SQLServer(FieldTypeAdapter): 
     965     
     966    def _create_str_storage(self, cls, key): 
     967        prop = getattr(cls, key) 
     968        size = prop.hints.get(u'Size', '255') 
     969        if size == 0 or size > 8000: 
     970            # 8000 *bytes* is the absolute upper limit, based on T_SQL docs 
     971            # for varchar. If there are further fields defined for the class, 
     972            # or the code page uses a double-byte character set, we still 
     973            # might exceed the max size (8060) for a record. We could calc 
     974            # the total requested record size, and adjust accordingly. For 
     975            # now, we just trust that units generally use a size of 0 to 
     976            # bump up to NTEXT (1 gig characters). 
     977            return u"NTEXT" 
     978        return u"VARCHAR(%s)" % size 
     979     
     980    # dict, list, and tuple will all be pickled in AdapterToADO 
     981    def coerce_dict(self, cls, key): return u"NTEXT" 
     982    def coerce_list(self, cls, key): return u"NTEXT" 
     983    coerce_str = _create_str_storage 
     984    def coerce_tuple(self, cls, key): return u"NTEXT" 
     985    coerce_unicode = _create_str_storage 
     986 
     987 
    974988class StorageManagerADO_SQLServer(StorageManagerADO): 
    975      
    976     def _create_str_storage(unitClass, key): 
    977         try: 
    978             prop = getattr(unitClass, key) 
    979             size = prop.hints[u'Size'] 
    980         except KeyError: 
    981             return u"VARCHAR(255)" 
    982         else: 
    983             if size == 0 or size > 8060: 
    984                 # 8060 is the absolute upper limit, based on the page size 
    985                 # of SQL server. If there are further fields defined for 
    986                 # the unitClass, we could exceed the max size for a record. 
    987                 # Perhaps someday we can calc the total requested record 
    988                 # size, and adjust accordingly. For now, we just trust that 
    989                 # units generally use a size of 0 to bump up to NTEXT. 
    990                 return u"NTEXT" 
    991             return u"VARCHAR(%s)" % size 
    992      
    993     createCoercions = {datetime.datetime: lambda x, y: u"TIMESTAMP", 
    994                        datetime.date: lambda x, y: u"DATE", 
    995                        datetime.time: lambda x, y: u"TIME", 
    996                        str: _create_str_storage, 
    997                        unicode: _create_str_storage, 
    998                        dict: lambda x, y: u"NTEXT", 
    999                        list: lambda x, y: u"NTEXT", 
    1000                        fixedpoint.FixedPoint: lambda x, y: u"FLOAT", 
    1001                        float: lambda x, y: u"FLOAT", 
    1002                        int: lambda x, y: u"INTEGER", 
    1003                        bool: lambda x, y: u"BIT", 
    1004                        } 
     989    createAdapter = FieldTypeAdapter_SQLServer() 
    1005990 
    1006991 
     
    10251010 
    10261011 
     1012class FieldTypeAdapter_MSAccess(FieldTypeAdapter): 
     1013     
     1014    def _create_str_storage(self, cls, key): 
     1015        prop = getattr(cls, key) 
     1016        size = prop.hints.get(u'Size', '255') 
     1017        if size == 0 or size > 255: 
     1018            # 255 chars is the upper limit for TEXT / VARCHAR in MS Access. 
     1019            # MEMO is 1 gigabyte when set programatically (only 64K when set 
     1020            # in Access UI). But then, 1 GB is the limit for the whole DB. 
     1021            return u"MEMO" 
     1022        return u"VARCHAR(%s)" % size 
     1023     
     1024    # dict, list, and tuple will all be pickled in AdapterToADO 
     1025    def coerce_dict(self, cls, key): return u"MEMO" 
     1026    def coerce_list(self, cls, key): return u"MEMO" 
     1027    coerce_str = _create_str_storage 
     1028    def coerce_tuple(self, cls, key): return u"MEMO" 
     1029    coerce_unicode = _create_str_storage 
     1030 
     1031 
    10271032class StorageManagerADO_MSAccess(StorageManagerADO): 
    10281033     
    10291034    decompiler = ADOSQLDecompiler_MSAccess 
    1030      
    1031     def _create_str_storage(unitClass, key): 
    1032         try: 
    1033             prop = getattr(unitClass, key) 
    1034             size = prop.hints[u'Size'] 
    1035         except KeyError: 
    1036             return u"VARCHAR(255)" 
    1037         else: 
    1038             if size == 0 or size > 255: 
    1039                 # 255 is the upper limit, based on the max size of 'Text' 
    1040                 # fields within MS Access. If there are further fields 
    1041                 # defined for the unitClass, we could exceed the max size 
    1042                 # for a record. Perhaps someday we can calc the total 
    1043                 # requested record size, and adjust accordingly. For now, 
    1044                 # we just trust that units generally use aSize of 0 to 
    1045                 # bump up to MEMO. 
    1046                 return u"MEMO" 
    1047             return u"VARCHAR(%s)" % size 
    1048      
    1049     createCoercions = {datetime.datetime: lambda x, y: u"TIMESTAMP", 
    1050                        datetime.date: lambda x, y: u"DATE", 
    1051                        datetime.time: lambda x, y: u"TIME", 
    1052                        str: _create_str_storage, 
    1053                        unicode: _create_str_storage, 
    1054                        dict: lambda x, y: u"MEMO", 
    1055                        list: lambda x, y: u"MEMO", 
    1056                        fixedpoint.FixedPoint: lambda x, y: u"FLOAT", 
    1057                        float: lambda x, y: u"FLOAT", 
    1058                        int: lambda x, y: u"INTEGER", 
    1059                        bool: lambda x, y: u"BIT", 
    1060                        } 
     1035    createAdapter = FieldTypeAdapter_MSAccess() 
     1036 
    10611037 
    10621038if __name__ == '__main__': 
  • trunk/storage/test_storeado.py

    r24 r25  
    99 
    1010smOptions = {u'Connect': ("PROVIDER=MICROSOFT.JET.OLEDB.4.0;" 
    11                            "DATA SOURCE=test.mdb;"),} 
     11                           "DATA SOURCE=test.mdb;"), 
     12             u'Expanded Columns': "Other.Bunch", 
     13             } 
    1214testSM = storeado.StorageManagerADO_MSAccess("test", arena, smOptions) 
    1315arena.stores['testSM'] = arena.defaultStore = testSM 
    1416 
    1517 
    16 class Things(dejavu.Unit): pass 
    17 Things.set_properties({"Name": unicode, 
    18                        "Size": int, 
    19                        "Date": datetime.date, 
    20                        "Time": datetime.time, 
    21                        "DateTime": datetime.datetime, 
    22                        }) 
     18class Things(dejavu.Unit): 
     19    Name = dejavu.UnitProperty("Name", unicode) 
     20    Size = dejavu.UnitProperty("Size", int) 
     21    Date = dejavu.UnitProperty("Date", datetime.date) 
     22    Time = dejavu.UnitProperty("Time", datetime.time) 
     23    DateTime = dejavu.UnitProperty("DateTime", datetime.datetime) 
    2324 
    2425class Animals(dejavu.Unit): pass 
     
    2829                        }) 
    2930arena.associate(Things, 'Size', Animals, 'Legs') 
     31 
     32class Other(dejavu.Unit): 
     33    Blob = dejavu.UnitProperty("Blob", list) 
     34    Bunch = dejavu.UnitProperty("Bunch", list) 
     35arena.register(Other) 
    3036 
    3137 
     
    8288            pass 
    8389        testSM.create_storage(Things) 
     90         
     91        try: 
     92            testSM.execute("DROP TABLE djvOther") 
     93        except pywintypes.com_error: 
     94            pass 
     95        testSM.create_storage(Other) 
    8496     
    8597    def test_recordset(self): 
     
    91103        self.assertEqual(len(data[0]), 4) 
    92104        rs.Close() 
     105     
     106    def test_expanded_columns(self): 
     107        o = Other(ID=1, Blob=[1, 2, 3], Bunch=['a', 'b', 'c']) 
     108        box = dejavu.Sandbox(arena) 
     109        box.memorize(o) 
     110        box.flush_all() 
     111        o = box.unit(Other, ID=1) 
     112        self.assertEqual(o.Blob, [1, 2, 3]) 
     113        self.assertEqual(o.Bunch, ['a', 'b', 'c']) 
    93114     
    94115    def test_unit_roundtrip(self): 
     
    179200              "([djvThings].[Date] = 3) and (([djvThings].[Qty] > 4) and ([djvThings].[Qty] < 20))", False) 
    180201 
     202 
    181203class AdapterTests(unittest.TestCase): 
    182204     
     
    188210        gaiter.Name = 'Presentation pair' 
    189211        gaiter.Size = 3 
    190         gaiter.Date = datetime.date(2000, 1, 1) 
     212        gaiter.Date = d = datetime.date(2000, 1, 1) 
    191213        # 59 should give rounding errors with divmod, which 
    192214        # AdapterFromADO needs to correct. 
    193         gaiter.Time = datetime.time(8, 15, 59) 
    194         gaiter.DateTime = datetime.datetime(2004, 7, 29, 5, 6, 7) 
     215        gaiter.Time = t = datetime.time(8, 15, 59) 
     216        gaiter.DateTime = dt = datetime.datetime(2004, 7, 29, 5, 6, 7) 
    195217        box.memorize(gaiter) 
    196218         
     
    199221        gaiter = box.unit(Things, Size=3) 
    200222        self.assertEqual(gaiter.Size, 3) 
    201         self.assertEqual(gaiter.Date, datetime.date(2000, 1, 1)
    202         self.assertEqual(gaiter.Time, datetime.time(8, 15, 59)
    203         self.assertEqual(gaiter.DateTime, datetime.datetime(2004, 7, 29, 5, 6, 7)
     223        self.assertEqual(gaiter.Date, d
     224        self.assertEqual(gaiter.Time, t
     225        self.assertEqual(gaiter.DateTime, dt
    204226 
    205227