Contact: fumanchu@aminus.org

Log in as guest/dejavu to create tickets

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

Changeset 43

Show
Ignore:
Timestamp:
12/27/04 07:17:49
Author:
fumanchu
Message:

1. Changed UnitProperty?.hints['Size'] to 'bytes'. SM's should now assume infinite bytes unless told otherwise.
2. Abstracted adapters into db.py.
3. Adapter coerce methods now take a coltype arg.
4. Changed safe_name functions to SM.identifier methods.
5. Bugfix: COMPARE_OP now uses op indices not values.
6. Moved len() from CALL_FUNCTION to decompiler.functions.
7. Added db.ConstWrapper? to help with LOAD_CONST corner cases.
8. Added MYSQL SM and test suite.

Files:

Legend:

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

    r42 r43  
    175175        is to inform Managers that would usually store unicode strings 
    176176        as strings of length 255, that a particular value should be 
    177         a larger object; this is done with a 'Size' mapping, such as: 
    178         hints = {u'Size': 0}, where 0 implies no limit. Canonical storage 
    179         hint names and implementation details may be found in storage.py 
     177        a larger object; this is done with a 'bytes' mapping, such as: 
     178        hints = {u'bytes': 0}, where 0 implies no limit. Canonical storage 
     179        hint names and implementation details may be found in /storage 
    180180        documentation. 
    181181    """ 
  • trunk/doc/framework.html

    r42 r43  
    8686<ol> 
    8787    <li>Adapters, which coerce values from Python types to database types 
    88         and back again.</li> 
     88        and back again. Base classes for DB Adapters can be found in 
     89        <tt>dejavu.storage.db</tt>.</li> 
    8990    <li>An SQLDecompiler, which converts dejavu <tt>Expression</tt> objects 
    9091        (essentially, Python lambdas) into SQL.</li> 
     
    9798<p>Generally, you will end up with three kinds of Adapters (subclasses of 
    9899<tt>storage.Adapter</tt>): one for converting Dejavu types to your database 
    99 types, another for the reverse, and probably a third to insert Dejavu 
    100 values (with proper quoting, etc.) into SQL statements for your database. 
     100types, another for the reverse (<tt>storage.db.AdapterFromDB</tt>), and 
     101probably a third to insert Dejavu values (with proper quoting, etc.) into 
     102SQL statements for your database (<tt>storage.db.AdapterToSQL</tt>). 
    101103The Adapter class provides a single public method, <tt>coerce(self, value,  
    102104valuetype=None)</tt>, which takes any value and attempts to return a new 
     
    110112<pre>    def coerce_int(self, value): 
    111113        return str(value)</pre> 
    112 Methods are named <tt>coerce_</tt>, plus the Python type name.</p> 
    113  
    114 <p>Your coercion method should receive a single value and 
    115 return that value, coerced to a type. <i>The same signature and process 
    116 hold whether you are coercing inbound or outbound data.</i> An outbound 
    117 adapter coerces from Dejavu types to database types. You supply a Dejavu 
    118 UnitProperty value to <tt>coerce</tt>, and the appropriate coercion method 
    119 will be selected based upon the <tt>type()</tt> of that value. An inbound 
    120 adapter, on the other hand, coerces from DB types to Dejavu types. Call 
    121 <tt>coerce</tt> with your database value <i>and</i> the <tt>valuetype</tt> 
    122 argument, which is then used to call the appropriate coercion method. That 
    123 method returns the value, coerced to <tt>type(valuetype)</tt>, which the 
    124 UnitProperty expects. In both cases, the name of each coercion method takes 
    125 the Dejavu/Python type name, not the database type name.</p> 
     114Methods are named <tt>coerce_</tt>, plus the Python type name. Again, you 
     115can most likely use methods in the base Adapter classes provided.</p> 
     116 
     117<p>Your coercion method should receive a single value and return that value, 
     118coerced to a type. An outbound adapter coerces from Dejavu types to database 
     119types. You supply a Dejavu UnitProperty value to <tt>coerce</tt>, and the 
     120appropriate coercion method will be selected based upon the <tt>type()</tt> 
     121of that value. An inbound adapter, on the other hand, coerces from DB types 
     122to Dejavu types. Call <tt>coerce</tt> with your database value <i>and</i> 
     123the <tt>valuetype</tt> argument, which is then used to call the appropriate 
     124coercion method. That method returns the value, coerced to 
     125<tt>type(valuetype)</tt>, which the UnitProperty expects. In both cases, 
     126the name of each coercion method takes the Dejavu/Python type name, not the 
     127database type name.</p> 
    126128 
    127129<p>If <tt>coerce</tt> cannot find a method for the appropriate Python type, 
  • trunk/doc/index.html

    r42 r43  
    8989            <li>Microsoft Access (Jet)</li> 
    9090            <li>Microsoft SQL Server</li> 
     91            <li>PostgreSQL</li> 
     92            <li>MySQL</li> 
    9193            <li>ODBC</li> 
    9294            <li>Shelve</li> 
  • trunk/doc/modeling.html

    r42 r43  
    128128for example, is to inform Managers that would usually store unicode strings 
    129129as strings of length 255, that a particular value should be a larger object; 
    130 this is done with a 'Size' mapping, such as <tt>hints = {u'Size': 0}</tt>, 
     130this is done with a 'bytes' mapping, such as <tt>hints = {u'bytes': 0}</tt>, 
    131131where 0 implies no limit.</p> 
    132132 
  • trunk/doc/storage.html

    r42 r43  
    125125</ul> 
    126126 
     127<h5>PostgreSQL (pyPgSQL)</h5> 
     128<p>This class was developed against PostgreSQL 8.0.0 rc-1. Configuration entries:</p> 
     129<ul> 
     130    <li><b>Class:</b> <tt>dejavu.storage.storepypgsql.StorageManagerPgSQL</tt></li> 
     131    <li><b>Connect:</b> A connect string of the form "k=v k=v". For example, 
     132        <tt>"host=localhost dbname=myapp user=postgres password=hilar1ous"</tt>. 
     133        See the <a href='http://www.postgresql.org/docs/current/static/libpq.html'>libpq</a> 
     134        docs for complete information.</li> 
     135    <li><b>Threaded:</b> Optional. If set to any value, a new connection 
     136        is established for each thread. Otherwise, a single connection 
     137        is used for all threads.</li> 
     138    <li><b>Prefix:</b> If specified, all tables in the database will 
     139        have names starting with this prefix. If not provided, it 
     140        defaults to "djv". This helps if you need to mix Dejavu tables 
     141        with tables from another application.</li> 
     142    <li><b>Create If Missing:</b> If not blank or missing, create the database 
     143        as needed.</p> 
     144</ul> 
     145 
     146<h5>MySQL (MySQLdb)</h5> 
     147<p>This class was developed against: 
     148    mysql  Ver 14.7 Distrib 4.1.8, for Win95/Win98 (i32). 
     149    Configuration entries:</p> 
     150<ul> 
     151    <li><b>Class:</b> <tt>dejavu.storage.storemysql.StorageManagerMySQL</tt></li> 
     152    <li>Connection arguments: any of "host", "user", "passwd", "db", "port", 
     153        "unix_socket", "client_flag".<br />See the 
     154        <a href='http://dev.mysql.com/doc/mysql/en/mysql_real_connect.html'>docs</a> 
     155        for complete info.</li> 
     156    <li><b>Threaded:</b> Optional. If set to any value, a new connection 
     157        is established for each thread. Otherwise, a single connection 
     158        is used for all threads.</li> 
     159    <li><b>Prefix:</b> If specified, all tables in the database will 
     160        have names starting with this prefix. If not provided, it 
     161        defaults to "djv". This helps if you need to mix Dejavu tables 
     162        with tables from another application.</li> 
     163    <li><b>Create If Missing:</b> If not blank or missing, create the database 
     164        as needed.</p> 
     165</ul> 
     166 
    127167<h5>ODBC</h5> 
    128168<p>This class doesn't currently work. It needs some updating. Configuration 
  • trunk/engines.py

    r42 r43  
    159159    Operation = RuleProperty(str) 
    160160    SetID = RuleProperty(int) 
    161     Operand = RuleProperty(str, False, hints = {u'Size': 0}) 
     161    Operand = RuleProperty(str, False, hints = {u'bytes': 0}) 
    162162    Sequence = RuleProperty(int) 
    163163    EngineID = dejavu.UnitProperty(int, index=True) 
  • trunk/storage/storeado.py

    r42 r43  
    2121import dejavu 
    2222from dejavu import storage, codewalk, logic 
     23from dejavu.storage import db 
    2324import recur 
    2425 
     
    3233adLockOptimistic = 3 
    3334adLockBatchOptimistic = 4 
     35 
     36adUseClient = 3 
    3437 
    3538adModeShareExclusive = 12 
     
    5659 
    5760 
    58 class AdapterFromADO(storage.Adapter): 
     61class AdapterToADOSQL(db.AdapterToSQL): 
     62    """Coerce Expression constants to ADO SQL.""" 
     63     
     64    escapes = [("'", "''"), ("%", "[%]"), ("_", "[_]")] 
     65     
     66    def coerce_datetime_datetime(self, value): 
     67        return (u'#%s/%s/%s %02d:%02d:%02d#' % 
     68                (value.month, value.day, value.year, 
     69                 value.hour, value.minute, value.second)) 
     70     
     71    def coerce_datetime_date(self, value): 
     72        return u'#%s/%s/%s#' % (value.month, value.day, value.year) 
     73     
     74    def coerce_datetime_time(self, value): 
     75        return u'#%02d:%02d:%02d#' % (value.hour, value.minute, value.second) 
     76 
     77 
     78class AdapterFromADO(db.AdapterFromDB): 
    5979    """Coerce incoming values from ADO to Dejavu datatypes.""" 
    6080     
    61     def __init__(self, unit=None): 
    62         self.unit = unit 
    63      
    64     def consume(self, key, value): 
    65         expectedType = self.unit.__class__.property_type(key) 
    66         value = self.coerce(value, expectedType) 
    67         # Set the attribute directly to avoid __set__ overhead. 
    68         self.unit._properties[key] = value 
    69      
    70     def pickle(self, value): 
    71         aType, value = value 
    72         if value is None: 
    73             return None 
    74          
    75         # Coerce to str for pickle.loads restriction. 
    76         value = str(value) 
    77         return pickle.loads(value) 
    78      
    79     def coerce_datetime_datetime(self, value): 
     81    def coerce_datetime_datetime(self, value, coltype): 
    8082        # Illegal Date/Time values will crash the 
    8183        # app when using value.Format(). Therefore, 
    8284        # grab the value and figure the date ourselves. 
    8385        # Use 1-second resolution only. 
    84         aType, value = value 
    85         if value is None: 
    86             return None 
    87         elif isinstance(value, basestring): 
     86        if isinstance(value, basestring): 
    8887            return datetime.datetime(int(value[0:4]), int(value[4:6]), 
    8988                                     int(value[6:8])) 
     
    9493            return datetime.datetime.combine(aDate, aTime) 
    9594     
    96     def coerce_datetime_date(self, value): 
     95    def coerce_datetime_date(self, value, coltype): 
    9796        # See coerce_datetime 
    98         aType, value = value 
    99         if value is None: 
    100             return None 
    101         elif isinstance(value, basestring): 
     97        if isinstance(value, basestring): 
    10298            return datetime.date(int(value[0:4]), int(value[4:6]), 
    10399                                 int(value[6:8])) 
     
    105101            return datetime.date.fromordinal(int(float(value)) + zeroHour) 
    106102     
    107     def coerce_datetime_time(self, value): 
     103    def coerce_datetime_time(self, value, coltype): 
    108104        # See coerce_datetime 
    109         aType, value = value 
    110         if value is None: 
    111             return None 
    112         else: 
    113             day, aTime = time_from_com(value) 
    114             return aTime 
    115      
    116     def coerce_datetime_timedelta(self, value): 
    117         aType, value = value 
    118         if value is None: 
    119             return None 
    120          
    121         days, seconds = divmod(value, 1) 
    122         return datetime.timedelta(days, int(seconds * 86400)) 
    123      
    124     coerce_dict = pickle 
    125      
    126     def coerce_fixedpoint_FixedPoint(self, value): 
    127         aType, value = value 
    128         if value is None: 
    129             return None 
    130         if aType == 0x06: 
     105        day, aTime = time_from_com(value) 
     106        return aTime 
     107     
     108    def coerce_fixedpoint_FixedPoint(self, value, coltype): 
     109        if coltype == 0x06: 
    131110            # Currency 
    132111            value = value[1] / 10000.0 
    133112        return fixedpoint.FixedPoint(value) 
    134113     
    135     def coerce_float(self, value): 
    136         aType, value = value 
    137         if value is None: 
    138             return None 
    139         if aType == 0x06: 
     114    def coerce_float(self, value, coltype): 
     115        if coltype == 0x06: 
    140116            # Currency 
    141117            value = value[1] / 10000.0 
    142118        return float(value) 
    143119     
    144     def coerce_int(self, value): 
    145         aType, value = value 
    146         if value is None: 
    147             return None 
    148         if aType == 0x0b: 
     120    def coerce_int(self, value, coltype): 
     121        if coltype == 0x0b: 
    149122            # Boolean 
    150123            return value != 0 
     
    152125     
    153126    coerce_bool = coerce_int 
    154     coerce_list = pickle 
    155      
    156     def coerce_long(self, value): 
    157         aType, value = value 
    158         if value is None: 
    159             return None 
    160         return long(value) 
    161      
    162     def coerce_str(self, value): 
    163         aType, value = value 
    164         if value is None: 
    165             return None 
    166         return str(value) 
    167      
    168     coerce_tuple = pickle 
    169      
    170     def coerce_unicode(self, value): 
    171         aType, value = value 
    172         if value is None: 
    173             return None 
     127     
     128    def coerce_unicode(self, value, coltype): 
    174129        if isinstance(value, unicode): 
    175130            # For some reason, inValue is already a unicode object. 
     
    236191 
    237192 
    238 class AdapterToADOSQL(storage.Adapter): 
    239     """Coerce Expression constants to ADO SQL.""" 
    240      
    241     def tostr(self, value): 
    242         return str(value) 
    243      
    244     def coerce_NoneType(self, value): 
    245         return "Null" 
    246      
    247     def coerce_bool(self, value): 
    248         if value: 
    249             return 'True' 
    250         return 'False' 
    251      
    252     def coerce_datetime_datetime(self, value): 
    253         return (u'#%s/%s/%s %02d:%02d:%02d#' % 
    254                 (value.month, value.day, value.year, 
    255                  value.hour, value.minute, value.second)) 
    256      
    257     def coerce_datetime_date(self, value): 
    258         return u'#%s/%s/%s#' % (value.month, value.day, value.year) 
    259      
    260     def coerce_datetime_time(self, value): 
    261         return u'#%02d:%02d:%02d#' % (value.hour, value.minute, value.second) 
    262      
    263     def coerce_datetime_timedelta(self, value): 
    264         float_val = value.days + (value.seconds / 86400.0) 
    265         return repr(float_val) 
    266      
    267     coerce_fixedpoint_FixedPoint = tostr 
    268     coerce_float = tostr 
    269     coerce_int = tostr 
    270      
    271     def coerce_list(self, value): 
    272         return "(" + ", ".join([self.coerce(x) for x in value]) + ")" 
    273      
    274     coerce_long = tostr 
    275      
    276     def coerce_str(self, value): 
    277         value = value.replace(u"'", u"''") 
    278         value = value.replace("%", "[%]") 
    279         value = value.replace("_", "[_]") 
    280         return "'" + value + "'" 
    281      
    282     coerce_tuple = coerce_list 
    283      
    284     coerce_unicode = coerce_str 
    285  
    286  
    287193def icontainedby(op1, op2, notin=False): 
    288     # This test doesn't work right, now that we use lists as 
    289     # well as tuples with IN. Need a way to mark field refs. 
    290     if op2.startswith("[") and op2.endswith("]"): 
     194    if isinstance(op1, db.ConstWrapper): 
    291195        # Looking for text in a field. Use Like (reverse terms). 
     196        # LIKE is case-insensitive in MS SQL Server. 
    292197        value = op2 + " Like '%" + op1[1:-1] + "%'" 
    293198    else: 
    294199        # Looking for field in (a, b, c) 
    295         value = op1 + " in " + op2 
     200        value = op1 + " in (" + ", ".join([AdapterToADOSQL().coerce(x) 
     201                                           for x in op2.basevalue]) + ")" 
    296202    if notin: 
    297203        value = "not " + value 
     
    324230                 dejavu.today: lambda: "DATEADD(dd, DATEDIFF(dd,0,getdate()), 0)", 
    325231                 dejavu.year: lambda x: "YEAR(" + x + ")", 
     232                 len: lambda x: "Len(" + x + ")", 
    326233                 } 
    327234     
     
    338245        result = self.stack[0] 
    339246        if result is cannot_represent: 
    340             result = 'True
     247            result = 'TRUE
    341248        return result, self.imperfect 
    342249     
     
    347254            term, operation = terms.pop() 
    348255            # All this checking of cannot_represent is done so that a 
    349             # function (like dejavu.iscurrentweek) can be labeled 
    350             # imperfect--all Units (which match the rest of the  
    351             # Expression) will be recalled. They can then be 
    352             # compared in expr.evaluate(unit). 
     256            # function can be labeled imperfect. For example, the function 
     257            # dejavu.iscurrentweek has no ADO SQL equivalent. All Units 
     258            # (which match the rest of the Expression) will be recalled. 
     259            # They can then be compared in expr.evaluate(unit). 
    353260            if term is not cannot_represent: 
    354261                if comp is cannot_represent: 
     
    382289        # Some constants are function or class objects, 
    383290        # which should not be coerced. 
    384         no_coerce = (FunctionType, type) 
    385         if isinstance(val, no_coerce): 
    386             pass 
    387         elif isinstance(val, type(len)): 
    388             val = str(val) 
    389         else: 
    390             val = self.adapter.coerce(val) 
     291        no_coerce = (FunctionType, type, 
     292                     type(len),       # <type 'builtin_function_or_method'> 
     293                     ) 
     294        if not isinstance(val, no_coerce): 
     295            val = db.ConstWrapper(val, self.adapter.coerce(val)) 
    391296        self.stack.append(val) 
    392297     
     
    430335                    self.imperfect = True 
    431336                    return 
    432             elif isinstance(func, basestring) and func == '<built-in function len>': 
    433                 self.stack.append("Len(" + args[0] + ")") 
    434                 return 
    435337             
    436338            if self.stack: 
     
    442344    def visit_COMPARE_OP(self, lo, hi): 
    443345        op2, op1 = self.stack.pop(), self.stack.pop() 
    444         op = self.sql_cmp_op[lo + (hi << 8)] 
    445         if op == 'in': 
    446             self.stack.append(icontainedby(op1, op2)) 
     346        op = lo + (hi << 8) 
     347        if op in (6, 7):     # in, not in 
     348            if isinstance(op1, db.ConstWrapper): 
     349                # Looking for text in a field. Use Like (reverse terms). 
     350                # LIKE is case-insensitive in MS SQL Server (and there 
     351                # doesn't seem to be a way around it). Mark imperfect, 
     352                # but use the imperfect, insensitive filter at least. 
     353                value = op2 + " Like '%" + op1[1:-1] + "%'" 
     354            else: 
     355                # Looking for field in (a, b, c) 
     356                value = op1 + " in (" + ", ".join([AdapterToADOSQL().coerce(x) 
     357                                                   for x in op2.basevalue]) + ")" 
     358            if op == 7: 
     359                value = "not " + value 
     360            self.stack.append(value) 
    447361            self.imperfect = True 
    448         elif op == 'not in': 
    449             self.stack.append(icontainedby(op1, op2, True)) 
    450             self.imperfect = True 
    451         elif op == '=' and op2 == 'Null': 
    452             self.stack.append(op1 + " Is Null") 
    453         elif op == '=' and op1 == 'Null': 
    454             self.stack.append(op2 + " Is Null") 
    455         elif op == '!=' and op2 == 'Null': 
    456             self.stack.append(op1 + " Is Not Null") 
    457         elif op == '!=' and op1 == 'Null': 
    458             self.stack.append(op2 + " Is Not Null") 
    459         else: 
    460             if op2.startswith("'") and op2.endswith("'"): 
     362        elif op1 == 'NULL': 
     363            if op == 2: 
     364                self.stack.append(op2 + " IS NULL") 
     365            elif op == 3: 
     366                self.stack.append(op2 + " IS NOT NULL") 
     367            else: 
     368                raise ValueError("Non-equality Null comparisons not allowed.") 
     369        elif op2 == 'NULL': 
     370            if op == 2: 
     371                self.stack.append(op1 + " IS NULL") 
     372            elif op == 3: 
     373                self.stack.append(op1 + " IS NOT NULL") 
     374            else: 
     375                raise ValueError("Non-equality Null comparisons not allowed.") 
     376        else: 
     377            if (isinstance(op2, db.ConstWrapper) 
     378                and isinstance(op2.basevalue, basestring)): 
    461379                # ADO comparison operators for strings are case-insensitive 
    462380                # by default. Rather than determine which columns in the DB 
    463381                # might be case-sensitive, just flag them all as imperfect. 
     382                # TODO: might be possible to cast both to varbinary, but 
     383                # that may cause problems with unicode columns. 
    464384                self.imperfect = True 
    465             self.stack.append(op1 + " " + op + " " + op2) 
     385            self.stack.append(op1 + " " + self.sql_cmp_op[op] + " " + op2) 
    466386     
    467387    def binary_op(self, op): 
     
    471391    def visit_BINARY_SUBSCR(self): 
    472392        # The only BINARY_SUBSCR used in Expressions should be kwargs[key]. 
     393        # TODO: provide string slicing? 
    473394        name = self.stack.pop() 
    474395        tos = self.stack.pop() 
     
    484405        op = self.stack.pop() 
    485406        if op is cannot_represent: 
    486             # Usually as a result of has(farClassName). 
    487407            self.stack.append(cannot_represent) 
    488408        else: 
    489409            self.stack.append("not (" + op + ")") 
    490  
    491  
    492 def safe_name(content): 
    493     return unicode(content).replace(u"_", u"") 
    494410 
    495411 
     
    506422        self.sql, self.imperfect = store.select(unitClass, expr) 
    507423     
    508     def field(self, key, row): 
    509         try: 
    510             col = self.colIndices[key] 
    511         except KeyError, x: 
    512             x.args += (key, self.unitClass.__name__) 
    513             raise x 
    514          
    515         return (self.fieldTypes[col], self.data[col][row]) 
    516      
    517424    def load_data(self): 
    518425        anRS = self.store.recordset(self.sql, adOpenForwardOnly, 
     
    525432        self.data = [] 
    526433        if not(anRS.BOF and anRS.EOF): 
    527 ##            anRS.MoveFirst() 
    528 ##            if not(anRS.BOF or anRS.EOF): 
    529434            # We tried .MoveNext() and lots of Fields.Item() calls. 
    530435            # Using GetRows() beats that time by about 2/3. 
     
    535440        s = self.store 
    536441        clsname = self.unitClass.__name__ 
    537         tbl = "%s_%s" % (s.prefix, safe_name(clsname)) 
    538442        self.load_data() 
    539443        if len(self.data) > 0: 
     
    545449                        # Grab the expanded data 
    546450                        try: 
    547                             rs = s.recordset(u"SELECT EXPVAL FROM [%s_%s_%s]" 
    548                                              % (tbl, 
    549                                                 safe_name(self.field('ID', row)[1]), 
    550                                                 safe_name(key))) 
     451                            col = self.colIndices['ID'] 
     452                            ID = self.data[col][row] 
     453                            rs = s.recordset(u"SELECT EXPVAL FROM %s" % 
     454                                             s.identifier(s.prefix, "_", 
     455                                                          clsname, "_", 
     456                                                          ID, "_", key)) 
    551457                        except pywintypes.com_error, x: 
    552458                            # This usually occurs because the parent Unit 
     
    566472                        unit._properties[key] = values 
    567473                    else: 
    568                         value = self.field(key, row) 
    569                         coercer.consume(key, value) 
     474                        try: 
     475                            col = self.colIndices[key] 
     476                        except KeyError, x: 
     477                            x.args += (key, self.unitClass.__name__) 
     478                            raise x 
     479                        coercer.consume(key, self.data[col][row], 
     480                                        self.fieldTypes[col]) 
    570481                # If our SQL is imperfect, don't yield it to the 
    571482                # caller unless it passes evaluate(). 
     
    598509                raise x 
    599510            else: 
    600                 coercer.consume(key, (self.fieldTypes[col], 
    601                                       self.data[col][row])) 
     511                coercer.consume(key, self.data[col][row], self.fieldTypes[col]) 
    602512     
    603513    def load_data(self): 
     
    610520        self.data = [] 
    611521        if not(anRS.BOF and anRS.EOF): 
    612 ##            anRS.MoveFirst() 
    613 ##            if not(anRS.BOF or anRS.EOF): 
    614522            # We tried .MoveNext() and lots of Fields.Item() calls. 
    615523            # Using GetRows() beats that time by about 2/3. 
     
    636544 
    637545 
    638 class FieldTypeAdapter(object): 
    639     """Return the SQL typename of a DB column.""" 
    640      
    641     def coerce(self, cls, key): 
    642         """coerce(cls, key) -> SQL typename for valuetype.""" 
    643         valuetype = cls.property_type(key) 
    644         mod = valuetype.__module__ 
    645         if mod == "__builtin__": 
    646             xform = "coerce_%s" % valuetype.__name__ 
    647         else: 
    648             xform = "coerce_%s_%s" % (mod, valuetype.__name__) 
    649         xform = xform.replace(".", "_") 
    650         try: 
    651             xform = getattr(self, xform) 
    652         except AttributeError: 
    653             raise TypeError("'%s' is not handled by %s." % 
    654                             (valuetype, self.__class__)) 
    655         return xform(cls, key) 
    656      
    657     def _create_str_storage(self, cls, key): 
    658         """This basic string handler does not know anything about the size 
    659         limitations of the particular database. You should use one of the 
    660         subclasses for your particular database if you need storage for 
    661         strings over 255 characters. 
    662         """ 
    663         prop = getattr(cls, key) 
    664         size = int(prop.hints.get(u'Size', '255')) 
    665         return u"VARCHAR(%s)" % size 
    666      
    667     def coerce_bool(self, cls, key): return u"BIT" 
    668      
    669     def coerce_datetime_datetime(self, cls, key): return u"TIMESTAMP" 
    670     def coerce_datetime_date(self, cls, key): return u"DATE" 
    671     def coerce_datetime_time(self, cls, key): return u"TIME" 
    672      
    673     coerce_dict = _create_str_storage 
    674      
    675     def coerce_fixedpoint_FixedPoint(self, cls, key): return u"FLOAT" 
    676     def coerce_float(self, cls, key): return u"FLOAT" 
    677     def coerce_int(self, cls, key): return u"INTEGER" 
    678      
    679     coerce_list = _create_str_storage 
    680     coerce_str = _create_str_storage 
    681     coerce_tuple = _create_str_storage 
    682     coerce_unicode = _create_str_storage 
    683  
    684  
    685546class StorageManagerADO(storage.StorageManager): 
    686547    """StoreManager to save and retrieve Units via ADO 2.7. 
     
    690551     
    691552    decompiler = ADOSQLDecompiler 
    692     createAdapter = FieldTypeAdapter() 
     553    createAdapter = db.FieldTypeAdapter() 
    693554    threaded = False 
    694555     
     
    728589         
    729590        self.reserve_lock = threading.Lock() 
     591     
     592    def identifier(self, *atoms): 
     593        ident = "[" + ''.join(map(str, atoms)) + "]" 
     594        return ident 
    730595     
    731596    def shutdown(self): 
     
    753618    def recordset(self, aQuery, cursorType=None, lockType=None): 
    754619        anRS = win32com.client.Dispatch(r'ADODB.Recordset') 
    755 ##        anRS.Cursorlocation = 3     # adUseClient; Use to obtain .Recordcount 
     620        # Uncomment the following line to obtain .Recordcount 
     621        # anRS.Cursorlocation = adUseClient 
    756622        if cursorType is None: 
    757623            cursorType = self.cursorType 
     
    786652                  } 
    787653        if len(spath) == 1: 
    788             params[u'child'] = u"[%(prefix)s%(right)s]" % params 
     654            params[u'child'] = self.identifier(params['prefix'], params['right']) 
    789655        else: 
    790656            params[u'child'] = u"(%s)" % self._join(spath) 
     
    807673            w, new_imp = self.where(cls, expr) 
    808674            imp |= new_imp 
    809             if w and w != "True": 
     675            if w and w != "TRUE": 
    810676                w = " WHERE %s AND %s" % (w, firstwhere) 
    811677            else: 
     
    813679             
    814680            cols += [(cls, k) for k in cls.properties()] 
    815             colnames = ["[%s%s].[%s]" % (self.prefix, colcls.__name__, k) 
     681            colnames = ["%s.%s" % (self.identifier(self.prefix, colcls.__name__), 
     682                                   self.identifier(k)) 
    816683                        for colcls, k in cols] 
    817684             
     
    821688     
    822689    def select(self, unitClass, expr, distinct_fields=None): 
    823         tablename = self.prefix + safe_name(unitClass.__name__) 
     690        tablename = self.identifier(self.prefix, unitClass.__name__) 
    824691        if distinct_fields: 
    825             distinct_fields = [u'[%s]' % x for x in distinct_fields] 
    826             sql = (u"SELECT DISTINCT %s FROM [%s]" % 
     692            distinct_fields = [self.identifier(x) for x in distinct_fields] 
     693            sql = (u"SELECT DISTINCT %s FROM %s" % 
    827694                   (u', '.join(distinct_fields), tablename)) 
    828695        else: 
    829             sql = u"SELECT * FROM [%s]" % tablename 
     696            sql = u"SELECT * FROM %s" % tablename 
    830697        w, i = self.where(unitClass, expr) 
    831698        if len(w) > 0: 
     
    864731                data = [] 
    865732                clsname = unit.__class__.__name__ 
    866                 anRS = self.recordset(u"SELECT ID FROM [%s%s];" % 
    867                                       (self.prefix, safe_name(clsname))) 
     733                anRS = self.recordset(u"SELECT ID FROM %s;" % 
     734                                      self.identifier(self.prefix, clsname)) 
    868735                if not (anRS.BOF and anRS.EOF): 
    869736                    data = anRS.GetRows()[0] 
     
    888755            clsname = cls.__name__ 
    889756            # Use a cursor always--makes mixed-quotes, newline, etc easier. 
    890             anRS = self.recordset("SELECT * FROM [%s%s] WHERE ID = %s" % 
    891                                   (self.prefix, safe_name(clsname), 
     757            anRS = self.recordset("SELECT * FROM %s WHERE ID = %s" % 
     758                                  (self.identifier(self.prefix, clsname), 
    892759                                   AdapterToADOSQL().coerce(unit.ID))) 
    893760            if anRS.EOF and anRS.BOF: 
     
    918785        """Save a field using a table specifically for that purpose.""" 
    919786        unitcls = unit.__class__ 
    920         table = ("%s_%s_%s_%s" % (self.prefix, safe_name(unitcls.__name__)
    921                                   safe_name(unit.ID), safe_name(key))
     787        table = self.identifier(self.prefix, "_", unitcls.__name__, "_"
     788                                unit.ID, "_", key
    922789         
    923790        conn = self.connection() 
     
    925792        # Just drop the old table and start with a new one. 
    926793        try: 
    927             self.execute((u"DROP TABLE [%s];" % table), conn) 
     794            self.execute((u"DROP TABLE %s;" % table), conn) 
    928795        except pywintypes.com_error, x: 
    929796            pass 
     
    931798        # Ugly, ugly hack to get NTEXT or MEMO as appropriate. The point 
    932799        # is, we want a large text field so we can pickle each item. 
    933         ftype = self.createAdapter.coerce_list(None, None
    934         self.execute(u"CREATE TABLE [%s] (EXPVAL %s);" % (table, ftype), conn) 
    935          
    936         ins = u"INSERT INTO [" + table + "] (EXPVAL) VALUES ('%s');" 
     800        ftype = self.createAdapter.coerce_list(unitcls, key
     801        self.execute(u"CREATE TABLE %s (EXPVAL %s);" % (table, ftype), conn) 
     802         
     803        ins = u"INSERT INTO " + table + " (EXPVAL) VALUES ('%s');" 
    937804        val = getattr(unit, key) 
    938805        if val: 
     
    946813        """Delete the unit.""" 
    947814        # Use a DELETE command instead of a cursor for better performance. 
    948         deleteStatement = (u"DELETE * FROM [%s%s] WHERE ID = %s" % 
    949                            (self.prefix, safe_name(unit.__class__.__name__), 
     815        deleteStatement = (u"DELETE * FROM %s WHERE ID = %s" % 
     816                           (self.identifier(self.prefix, unit.__class__.__name__), 
    950817                            AdapterToADOSQL().coerce(unit.ID))) 
    951818        self.execute(deleteStatement) 
    952819     
    953820    def create_storage(self, unitClass): 
    954         clsname = safe_name(unitClass.__name__) 
    955          
    956821        coerce = self.createAdapter.coerce 
    957822        fields = [] 
    958823        for key in unitClass.properties(): 
    959824            if (unitClass.__name__, key) not in self.expanded_columns: 
    960                 fields.append(u"[%s] %s" % (key, coerce(unitClass, key))) 
    961         self.execute(u"CREATE TABLE [%s%s] (%s)" % 
    962                      (self.prefix, clsname, ", ".join(fields))) 
     825                fields.append(u"%s %s" % (self.identifier(key), 
     826                                          coerce(unitClass, key))) 
     827        self.execute(u"CREATE TABLE %s (%s)" % 
     828                     (self.identifier(self.prefix, unitClass.__name__), 
     829                      ", ".join(fields))) 
    963830         
    964831        for index in unitClass.indices(): 
    965             self.execute(u"CREATE INDEX [%si%s%s] ON [%s%s] (%s ASC)" 
    966                          % (self.prefix, clsname, safe_name(index), 
    967                             self.prefix, clsname, index)) 
     832            self.execute(u"CREATE INDEX %s ON %s (%s ASC)" 
     833                         % (self.identifier(self.prefix, "i", 
     834                                            unitClass.__name__, index), 
     835                            self.identifier(self.prefix, unitClass.__name__), 
     836                            index)) 
    968837     
    969838    def distinct(self, cls, fields, expr=None): 
     
    998867                expectedType = cls.property_type(field) 
    999868                actualType = fieldTypes[col] 
    1000                 coerced_row = [coerce((actualType, val), expectedType) 
     869                coerced_row = [coerce(val, actualType, expectedType) 
    1001870                               for val in data[col]] 
    1002871                coerced_data.append(coerced_row) 
     
    1012881 
    1013882 
    1014 class FieldTypeAdapter_SQLServer(FieldTypeAdapter): 
    1015      
    1016     def _create_str_storage(self, cls, key): 
     883class FieldTypeAdapter_SQLServer(db.FieldTypeAdapter): 
     884     
     885    def coerce_str(self, cls, key): 
     886        # The bytes hint shall not reflect the usual 4-byte base for varchar. 
    1017887        prop = getattr(cls, key) 
    1018         size = int(prop.hints.get(u'Size', '255')) 
    1019         if size == 0 or size > 8000: 
     888        bytes = int(prop.hints.get(u'bytes', '0')) 
     889        if bytes and bytes < 8000: 
     890            return u"VARCHAR(%s)" % bytes 
     891        else: 
    1020892            # 8000 *bytes* is the absolute upper limit, based on T_SQL docs 
    1021893            # for varchar. If there are further fields defined for the class, 
     
    1026898            # bump up to NTEXT (1 gig characters). 
    1027899            return u"NTEXT" 
    1028         return u"VARCHAR(%s)" % size 
    1029      
    1030     # dict, list, and tuple will all be pickled in AdapterToADO 
    1031     def coerce_dict(self, cls, key): return u"NTEXT" 
    1032     def coerce_list(self, cls, key): return u"NTEXT" 
    1033     coerce_str = _create_str_storage 
    1034     def coerce_tuple(self, cls, key): return u"NTEXT" 
    1035     coerce_unicode = _create_str_storage 
    1036900 
    1037901 
     
    1063927                 dejavu.today: lambda: "DateValue(Now())", 
    1064928                 dejavu.year: lambda x: "Year(" + x + ")", 
     929                 len: lambda x: "Len(" + x + ")", 
    1065930                 } 
    1066931 
    1067932 
    1068 class FieldTypeAdapter_MSAccess(FieldTypeAdapter): 
    1069      
    1070     def _create_str_storage(self, cls, key): 
     933class FieldTypeAdapter_MSAccess(db.FieldTypeAdapter): 
     934     
     935    def coerce_str(self, cls, key): 
     936        # The bytes hint shall not reflect the usual 4-byte base for varchar. 
    1071937        prop = getattr(cls, key) 
    1072         size = int(prop.hints.get(u'Size', '255')) 
    1073         if size == 0 or size > 255: 
     938        bytes = int(prop.hints.get(u'bytes', '0')) 
     939        if bytes and bytes < 8000: 
     940            return u"VARCHAR(%s)" % bytes 
     941        else: 
    1074942            # 255 chars is the upper limit for TEXT / VARCHAR in MS Access. 
    1075943            # MEMO is 1 gigabyte when set programatically (only 64K when set 
    1076944            # in Access UI). But then, 1 GB is the limit for the whole DB. 
    1077945            return u"MEMO" 
    1078         return u"VARCHAR(%s)" % size 
    1079      
    1080     # dict, list, and tuple will all be pickled in AdapterToADO 
    1081     def coerce_dict(self, cls, key): return u"MEMO" 
    1082     def coerce_list(self, cls, key): return u"MEMO" 
    1083     coerce_str = _create_str_storage 
    1084     def coerce_tuple(self, cls, key): return u"MEMO" 
    1085     coerce_unicode = _create_str_storage 
    1086946 
    1087947 
  • trunk/storage/storeodbc.py

    r42 r43  
    471471        subclasses for your particular database if you need storage for 
    472472        strings over 255 characters.""" 
    473         try: 
    474             prop = getattr(unitClass, key) 
    475             size = prop.hints[u'Size'] 
    476             return u"VARCHAR(%s)" % size 
    477         except KeyError: 
    478             return u"VARCHAR(255)" 
     473        prop = getattr(unitClass, key) 
     474        size = prop.hints.get(u'bytes', '255') 
     475        return u"VARCHAR(%s)" % size 
    479476     
    480477    createCoercions = {datetime.datetime: lambda x, y: u"TIMESTAMP", 
  • trunk/storage/storepypgsql.py

    r42 r43  
    1818import dejavu 
    1919from dejavu import storage, codewalk, logic 
     20from dejavu.storage import db 
    2021import recur 
    2122 
    2223 
    23 class AdapterFromPgSQL(storage.Adapter): 
    24     """Coerce incoming values from libpq to Dejavu datatypes.""" 
    25      
    26     def __init__(self, unit=None): 
    27         self.unit = unit 
    28      
    29     def consume(self, key, value): 
    30         expectedType = self.unit.__class__.property_type(key) 
    31         value = self.coerce(value, expectedType) 
    32         # Set the attribute directly to avoid __set__ overhead. 
    33         self.unit._properties[key] = value 
    34      
    35     def pickle(self, value): 
    36         aType, value = value 
    37         if value is None: 
    38             return None 
    39          
    40         # Coerce to str for pickle.loads restriction. 
    41         value = str(value) 
    42         return pickle.loads(value) 
    43      
    44     def coerce_bool(self, value): 
    45         aType, value = value 
    46         if value is None: 
    47             return None 
    48         return bool(value) 
    49      
    50     def coerce_datetime_datetime(self, value): 
    51         aType, value = value 
    52         if value is None: 
    53             return None 
    54          
    55         chunks = (value[0:4], value[5:7], value[8:10], 
    56                   value[11:13], value[14:16], value[17:19]) 
    57         return datetime.datetime(*map(int, chunks)) 
    58      
    59     def coerce_datetime_date(self, value): 
    60         aType, value = value 
    61         if value is None: 
    62             return None 
    63          
    64         chunks = (value[0:4], value[5:7], value[8:10]) 
    65         return datetime.date(*map(int, chunks)) 
    66      
    67     def coerce_datetime_time(self, value): 
    68         aType, value = value 
    69         if value is None: 
    70             return None 
    71          
    72         chunks = (value[9:11], value[12:14], value[15:17]) 
    73         return datetime.time(*map(int, chunks)) 
    74      
    75     def coerce_datetime_timedelta(self, value): 
    76         aType, value = value 
    77         if value is None: 
    78             return None 
    79          
    80         days, seconds = divmod(value, 1) 
    81         return datetime.timedelta(days, int(seconds * 86400)) 
    82      
    83     coerce_dict = pickle 
    84      
    85     def coerce_fixedpoint_FixedPoint(self, value): 
    86         aType, value = value 
    87         if value is None: 
    88             return None 
    89         return fixedpoint.FixedPoint(value) 
    90      
    91     def coerce_float(self, value): 
    92         aType, value = value 
    93         if value is None: 
    94             return None 
    95         return float(value) 
    96      
    97     def coerce_int(self, value): 
    98         aType, value = value 
    99         if value is None: 
    100             return None 
    101         return int(value) 
    102      
    103     coerce_list = pickle 
    104      
    105     def coerce_long(self, value): 
    106         aType, value = value 
    107         if value is None: 
    108             return None 
    109         return long(value) 
    110      
    111     def coerce_str(self, value): 
    112         aType, value = value 
    113         if value is None: 
    114             return None 
    115         return str(value) 
    116      
    117     coerce_tuple = pickle 
    118      
    119     def coerce_unicode(self, value): 
    120         aType, value = value 
    121         if value is None: 
    122             return None 
    123          
    124         # We should really inspect the pg_database system catalog 
    125         # to determine the encoding. Meh. 
    126         return unicode(value, "ASCII") 
    127  
    128  
    129 class AdapterToPgSQL(storage.Adapter): 
    130     """Coerce Expression constants to Pg SQL.""" 
    131      
    132     def tostr(self, value): 
    133         return str(value) 
    134      
    135     def coerce_NoneType(self, value): 
    136         return "NULL" 
    137      
    138     def coerce_bool(self, value): 
    139         if value: 
    140             return 'TRUE' 
    141         return 'FALSE' 
    142      
    143     def coerce_datetime_datetime(self, value): 
    144         return (u"'%04d-%02d-%02d %02d:%02d:%02d'" % 
    145                 (value.year, value.month, value.day, 
    146                  value.hour, value.minute, value.second)) 
    147      
    148     def coerce_datetime_date(self, value): 
    149         return u"'%04d-%02d-%02d'" % (value.year, value.month, value.day) 
    150      
    151     def coerce_datetime_time(self, value): 
    152         return u"'%02d:%02d:%02d'" % (value.hour, value.minute, value.second) 
    153      
    154     def coerce_datetime_timedelta(self, value): 
    155         float_val = value.days + (value.seconds / 86400.0) 
    156         return repr(float_val) 
    157      
    158     coerce_fixedpoint_FixedPoint = tostr 
    159     coerce_float = tostr 
    160     coerce_int = tostr 
    161      
    162     def coerce_list(self, value): 
    163         return "(" + ", ".join([self.coerce(x) for x in value]) + ")" 
    164      
    165     coerce_long = tostr 
    166      
    167     def coerce_str(self, value): 
    168         value = value.replace("'", "''") 
    169         value = value.replace("\\", r"\\") 
    170         value = value.replace("%", r"\%") 
    171         value = value.replace("_", r"\_") 
    172         return "'" + value + "'" 
    173      
    174     coerce_tuple = coerce_list 
    175      
    176     coerce_unicode = coerce_str 
     24AdapterToPgSQL = db.AdapterToSQL() 
     25AdapterFromPg = db.AdapterFromDB 
    17726 
    17827 
    17928def containedby(op1, op2, notin=False): 
    180     # This test doesn't work right, now that we use lists as 
    181     # well as tuples with IN. Need a way to mark field refs. 
    182     if op2.startswith('"') and op2.endswith('"'): 
     29    if isinstance(op1, db.ConstWrapper): 
    18330        # Looking for text in a field. Use Like (reverse terms). 
    18431        value = op2 + " LIKE '%" + op1[1:-1] + "%'" 
    18532    else: 
    18633        # Looking for field in (a, b, c) 
    187         value = op1 + " IN " + op2 
     34        atoms = [AdapterToPgSQL.coerce(x) for x in op2.basevalue] 
     35        value = op1 + " IN (" + ", ".join(atoms) + ")" 
    18836    if notin: 
    18937        value = "NOT " + value 
     
    19139 
    19240def icontainedby(op1, op2, notin=False): 
    193     # This test doesn't work right, now that we use lists as 
    194     # well as tuples with IN. Need a way to mark field refs. 
    195     if op2.startswith('"') and op2.endswith('"'): 
     41    if isinstance(op1, db.ConstWrapper): 
    19642        # Looking for text in a field. Use Like (reverse terms). 
    19743        value = op2 + " ILIKE '%" + op1[1:-1] + "%'" 
    19844    else: 
    19945        # Looking for field in (a, b, c) 
    200         value = "lower(" + op1 + ") IN " + op2.lower() 
     46        atoms = [AdapterToPgSQL.coerce(x).lower() for x in op2.basevalue] 
     47        value = "LOWER(" + op1 + ") IN (" + ", ".join(atoms) + ")" 
    20148    if notin: 
    20249        value = "NOT " + value 
     
    21057 
    21158class PgSQLDecompiler(codewalk.LambdaDecompiler): 
    212     """PgSQLDecompiler(store, unitClass, expr, adapter=AdapterToPgSQL()). 
     59    """PgSQLDecompiler(store, unitClass, expr, adapter=AdapterToPgSQL). 
    21360     
    21461    Produce SQL from a supplied Expression object, with a lambda of the form: 
     
    23178                 dejavu.today: lambda: "CURRENT_DATE", 
    23279                 dejavu.year: lambda x: "date_part('year', " + x + ")", 
     80                 len: lambda x: "length(" + x + ")", 
    23381                 } 
    23482     
    235     def __init__(self, store, unitClass, expr, adapter=AdapterToPgSQL()): 
     83    def __init__(self, store, unitClass, expr, adapter=AdapterToPgSQL): 
    23684        self.tablename = store.prefix + unitClass.__name__ 
    23785        self.expr = expr 
     
    254102            term, operation = terms.pop() 
    255103            # All this checking of cannot_represent is done so that a 
    256             # function (like dejavu.iscurrentweek) can be labeled 
    257             # imperfect--all Units (which match the rest of the  
    258             # Expression) will be recalled. They can then be 
    259             # compared in expr.evaluate(unit). 
     104            # function can be labeled imperfect. For example, the function 
     105            # dejavu.iscurrentweek has no PG SQL equivalent. All Units 
     106            # (which match the rest of the Expression) will be recalled. 
     107            # They can then be compared in expr.evaluate(unit). 
    260108            if term is not cannot_represent: 
    261109                if comp is cannot_represent: 
     
    289137        # Some constants are function or class objects, 
    290138        # which should not be coerced. 
    291         no_coerce = (FunctionType, type) 
    292         if isinstance(val, no_coerce): 
    293             pass 
    294         elif isinstance(val, type(len)): 
    295             val = str(val) 
    296         else: 
    297             val = self.adapter.coerce(val) 
     139        no_coerce = (FunctionType, type, 
     140                     type(len),       # <type 'builtin_function_or_method'> 
     141                     ) 
     142        if not isinstance(val, no_coerce): 
     143            val = db.ConstWrapper(val, self.adapter.coerce(val)) 
    298144        self.stack.append(val) 
    299145     
     
    335181                    self.stack.append(tos + " LIKE '%" + args[0][1:-1] + "'") 
    336182                    return 
    337             elif isinstance(func, basestring) and func == '<built-in function len>': 
    338                 self.stack.append("length(" + args[0] + ")") 
    339183                return 
    340184             
     
    347191    def visit_COMPARE_OP(self, lo, hi): 
    348192        op2, op1 = self.stack.pop(), self.stack.pop() 
    349         op = self.sql_cmp_op[lo + (hi << 8)] 
    350         if op == 'in': 
    351             self.stack.append(containedby(op1, op2)) 
     193        op = lo + (hi << 8) 
     194        if op in (6, 7):     # in, not in 
     195            self.stack.append(containedby(op1, op2, op == 7)) 
    352196            self.imperfect = True 
    353         elif op == 'not in': 
    354             self.stack.append(containedby(op1, op2, True)) 
    355             self.imperfect = True 
    356         elif op == '=' and op2 == 'NULL': 
    357             self.stack.append(op1 + " IS NULL") 
    358         elif op == '=' and op1 == 'NULL': 
    359             self.stack.append(op2 + " IS NULL") 
    360         elif op == '!=' and op2 == 'NULL': 
    361             self.stack.append(op1 + " IS NOT NULL") 
    362         elif op == '!=' and op1 == 'NULL': 
    363             self.stack.append(op2 + " IS NOT NULL") 
    364         else: 
    365             self.stack.append(op1 + " " + op + " " + op2) 
     197        elif op1 == 'NULL': 
     198            if op == 2: 
     199                self.stack.append(op2 + " IS NULL") 
     200            elif op == 3: 
     201                self.stack.append(op2 + " IS NOT NULL") 
     202            else: 
     203                raise ValueError("Non-equality Null comparisons not allowed.") 
     204        elif op2 == 'NULL': 
     205            if op == 2: 
     206                self.stack.append(op1 + " IS NULL") 
     207            elif op == 3: 
     208                self.stack.append(op1 + " IS NOT NULL") 
     209            else: 
     210                raise ValueError("Non-equality Null comparisons not allowed.") 
     211        else: 
     212            # Comparison operators for strings are case-sensitive in PG. 
     213            self.stack.append(op1 + " " + self.sql_cmp_op[op] + " " + op2) 
    366214     
    367215    def binary_op(self, op): 
     
    390238 
    391239 
    392 def safe_name(content): 
    393     if len(content) > 63: 
    394         warnings.warn("Identifier is longer than 63 characters. Most " 
    395                       "installations of Postgres are limited to 63.\n" 
    396                       "See NAMEDATALEN.") 
    397     return content 
    398  
    399  
    400240class StoreIteratorPgSQL(object): 
    401241    """Iterator for populating Units from storage.""" 
     
    418258            for row in xrange(res.ntuples): 
    419259                unit = self.unitClass() 
    420                 coercer = AdapterFromPgSQL(unit) 
     260                coercer = AdapterFromPg(unit) 
    421261                for key in unit.__class__.properties(): 
    422262                    index, ftype = columns[key] 
    423263                    value = res.getvalue(row, index) 
    424264                    try: 
    425                         coercer.consume(key, (ftype, value)
     265                        coercer.consume(key, value, ftype
    426266                    except Exception, x: 
    427267                        x.args += (key, ftype, value) 
     
    434274 
    435275 
    436 class FieldTypeAdapter(object): 
    437     """Return the SQL typename of a DB column.""" 
    438      
    439     def coerce(self, cls, key): 
    440         """coerce(cls, key) -> SQL typename for valuetype.""" 
    441         valuetype = cls.property_type(key) 
    442         mod = valuetype.__module__ 
    443         if mod == "__builtin__": 
    444             xform = "coerce_%s" % valuetype.__name__ 
    445         else: 
    446             xform = "coerce_%s_%s" % (mod, valuetype.__name__) 
    447         xform = xform.replace(".", "_") 
    448         try: 
    449             xform = getattr(self, xform) 
    450         except AttributeError: 
    451             raise TypeError("'%s' is not handled by %s." % 
    452                             (valuetype, self.__class__)) 
    453         return xform(cls, key) 
    454      
    455     def _create_str_storage(self, cls, key): 
    456         """This basic string handler does not know anything about the size 
    457         limitations of the particular database. You should use one of the 
    458         subclasses for your particular database if you need storage for 
    459         strings over 255 characters. 
    460         """ 
    461         prop = getattr(cls, key) 
    462         size = int(prop.hints.get(u'Size', '0')) 
    463         if size: 
    464             return u"varchar(%s)" % size 
    465         else: 
    466             return u"text" 
    467      
    468     def coerce_bool(self, cls, key): return u"boolean" 
    469      
    470     def coerce_datetime_datetime(self, cls, key): return u"timestamp" 
    471     def coerce_datetime_date(self, cls, key): return u"date" 
    472     def coerce_datetime_time(self, cls, key): return u"time" 
    473      
    474     def coerce_datetime_timedelta(self, cls, key): 
    475         # I was seriously disinterested in writing a parser for interval. 
    476         return u"float8" 
    477      
    478     def coerce_decimal(self, cls, key): return u"numeric" 
    479      
    480     coerce_dict = _create_str_storage 
    481      
    482     def coerce_fixedpoint_FixedPoint(self, cls, key): return u"float8" 
    483     def coerce_float(self, cls, key): return u"float8" 
    484     def coerce_int(self, cls, key): return u"bigint" 
    485      
    486     def coerce_long(self, cls, key): return u"decimal" 
    487      
    488     coerce_list = _create_str_storage 
    489     coerce_str = _create_str_storage 
    490     coerce_tuple = _create_str_storage 
    491     coerce_unicode = _create_str_storage 
    492  
    493  
    494276class StorageManagerPgSQL(storage.StorageManager): 
    495277    """StoreManager to save and retrieve Units via pyPgSQL 1.35.""" 
    496278     
    497279    decompiler = PgSQLDecompiler 
    498     createAdapter = FieldTypeAdapter() 
     280    createAdapter = db.FieldTypeAdapter() 
    499281    threaded = False 
    500282     
     
    524306        self.prefix = allOptions.get(u'Prefix', u"djv") 
    525307        self.reserve_lock = threading.Lock() 
     308     
     309    def identifier(self, *atoms): 
     310        ident = '"' + ''.join(map(str, atoms)).replace('"', '""') + '"' 
     311        if len(ident) > 63: 
     312            warnings.warn("Identifier is longer than 63 characters. Most " 
     313                          "installations of Postgres are limited to 63. " 
     314                          "See NAMEDATALEN.") 
     315        return ident 
    526316     
    527317    def shutdown(self): 
     
    553343            tmplconn += "%s=%s " % (k, v) 
    554344        conn = libpq.PQconnectdb(tmplconn) 
    555         self.execute('CREATE DATABASE "%s"' % self.dbname, conn) 
     345        self.execute('CREATE DATABASE %s' % self.identifier(self.dbname), conn) 
    556346     
    557347    def select(self, unitClass, expr, distinct_fields=None): 
    558         tablename = self.prefix + safe_name(unitClass.__name__) 
     348        tablename = self.identifier(self.prefix, unitClass.__name__) 
    559349        if distinct_fields: 
    560             distinct_fields = [u'"%s"' % x for x in distinct_fields] 
    561             sql = (u'SELECT DISTINCT %s FROM "%s"' % 
     350            distinct_fields = [self.identifier(x) for x in distinct_fields] 
     351            sql = (u'SELECT DISTINCT %s FROM %s' % 
    562352                   (u', '.join(distinct_fields), tablename)) 
    563353        else: 
    564             sql = u'SELECT * FROM "%s"' % tablename 
     354            sql = u'SELECT * FROM %s' % tablename 
    565355        w, i = self.where(unitClass, expr) 
    566356        if len(w) > 0: 
     
    595385        The ID should be supplied by UnitSequencers via reserve(). 
    596386        """ 
     387        clsname = unit.__class__.__name__ 
     388        tblname = self.identifier(self.prefix, clsname) 
     389        id = self.identifier("ID") 
    597390        self.reserve_lock.acquire() 
    598391        try: 
    599392            if unit.ID is None: 
    600393                data = [] 
    601                 clsname = unit.__class__.__name__ 
    602                 tblname = '"%s%s"' % (self.prefix, safe_name(clsname)) 
    603                 res = self.execute(u'SELECT "ID" FROM %s;' % tblname) 
     394                res = self.execute(u'SELECT %s FROM %s;' % (id, tblname)) 
    604395                if res.resultType != libpq.EMPTY_QUERY: 
    605396                    data = [res.getvalue(row, 0) for row in xrange(res.ntuples)] 
    606397                unit.ID = unit.sequencer.next(data) 
    607                  
    608                 self.execute('INSERT INTO %s ("ID") VALUES (%s)' % 
    609                              (tblname, AdapterToPgSQL().coerce(unit.ID))) 
     398             
     399            self.execute('INSERT INTO %s (%s) VALUES (%s)' % 
     400                         (tblname, id, AdapterToPgSQL.coerce(unit.ID))) 
    610401        finally: 
    611402            self.reserve_lock.release() 
     
    616407            cls = unit.__class__ 
    617408            clsname = cls.__name__ 
    618             tablename = '"%s%s"' % (self.prefix, safe_name(clsname)
     409            tablename = self.identifier(self.prefix, clsname
    619410             
    620             fmt = AdapterToPgSQL() 
    621411            parms = [] 
    622412            for key in cls.properties(): 
    623413                if key != "ID": 
    624                     val = fmt.coerce(getattr(unit, key)) 
    625                     parms.append('"%s" = %s' % (key, val)) 
    626             sql = ('UPDATE %s SET %s WHERE "ID" = %s' % 
     414                    val = AdapterToPgSQL.coerce(getattr(unit, key)) 
     415                    parms.append('%s = %s' % (self.identifier(key), val)) 
     416            sql = ('UPDATE %s SET %s WHERE %s = %s' % 
    627417                   (tablename, u", ".join(parms), 
    628                     fmt.coerce(unit.ID, cls.property_type("ID")))) 
     418                    self.identifier("ID"), 
     419                    AdapterToPgSQL.coerce(unit.ID, cls.property_type("ID")))) 
    629420            self.execute(sql) 
    630421            unit.cleanse() 
     
    633424        """Delete the unit.""" 
    634425        # Use a DELETE command instead of a cursor for better performance. 
    635         deleteStatement = (u'DELETE * FROM "%s%s" WHERE "ID" = %s' % 
    636                            (self.prefix, safe_name(unit.__class__.__name__), 
    637                             AdapterToPgSQL().coerce(unit.ID))) 
     426        deleteStatement = (u'DELETE * FROM %s WHERE %s = %s' % 
     427                           (self.identifier(self.prefix, unit.__class__.__name__), 
     428                            self.identifier("ID"), 
     429                            AdapterToPgSQL.coerce(unit.ID))) 
    638430        self.execute(deleteStatement) 
    639431     
    640432    def create_storage(self, unitClass): 
    641         clsname = safe_name(unitClass.__name__) 
    642         tblname = '"%s%s"' % (self.prefix, clsname) 
     433        tblname = self.identifier(self.prefix, unitClass.__name__) 
    643434         
    644435        coerce = self.createAdapter.coerce 
    645436        fields = [] 
    646437        for key in unitClass.properties(): 
    647             fields.append(u'"%s" %s' % (key, coerce(unitClass, key))) 
     438            fields.append(u'%s %s' % (self.identifier(key), 
     439                                      coerce(unitClass, key))) 
    648440        try: 
    649441            self.execute(u'CREATE TABLE %s (%s)' % (tblname, ", ".join(fields))) 
     
    653445        else: 
    654446            for index in unitClass.indices(): 
    655                 indexname = safe_name("%si%s%s" % (self.prefix, clsname, index)) 
    656                 self.execute(u'CREATE INDEX "%s" ON %s ("%s")' 
    657                              % (indexname, tblname, safe_name(index))) 
     447                indexname = self.identifier(self.prefix, "i", 
     448                                            unitClass.__name__, index) 
     449                self.execute(u'CREATE INDEX %s ON %s (%s)' 
     450                             % (indexname, tblname, self.identifier(index))) 
    658451     
    659452    def distinct(self, cls, fields, expr=None): 
     
    676469            return [] 
    677470         
    678         coerce = AdapterFromPgSQL().coerce 
     471        coerce = AdapterFromPg().coerce 
    679472        data = [] 
    680473        for row in xrange(res.ntuples): 
     
    683476                expectedType = cls.property_type(field[i]) 
    684477                actualType = res.ftype(i) 
    685                 val = coerce((actualType, res.getvalue(row, i)),expectedType) 
     478                val = coerce(res.getvalue(row, i), actualType, expectedType) 
    686479                coerced_row.append(val) 
    687480            data.append(coerced_row) 
  • trunk/storage/test_storeado.py

    r42 r43  
    3535         
    3636        # Test select all 
    37         sel(lambda x: True, u"SELECT * FROM [djvAnimal] WHERE True", False) 
     37        sel(lambda x: True, u"SELECT * FROM [djvAnimal] WHERE TRUE", False) 
    3838         
    3939        # Test now(), today(), year() 
     
    123123                 (datetime.date(2001, 11, 15), '#11/15/2001#'), 
    124124                 (datetime.time(6, 30), '#06:30:00#'), 
    125                  (True, 'True'), 
    126                  (None, 'Null'), 
     125                 (True, 'TRUE'), 
     126                 (None, 'NULL'), 
    127127                 ] 
    128128        for initial, final in pairs: 
     
    136136        trial(lambda x: x.Date == 3, "[djvAnimal].[Date] = 3", False) 
    137137        trial(lambda x, **kw: (x.a == 3) and ((x.b > 1) or (x.b < -10)), 
    138               u'([djvAnimal].[a] = 3) and (([djvAnimal].[b] > 1) or ([djvAnimal].[b] < -10))', False) 
     138              u'([djvAnimal].[a] = 3) and (([djvAnimal].[b] > 1) or ' 
     139              u'([djvAnimal].[b] < -10))', False) 
    139140        trial(lambda x, **kw: (x.Group == 3) and not (x.Name.startswith("_")) 
    140141              and not (x.Name.endswith('test')), 
    141               u"([djvAnimal].[Group] = 3) and ((not ([djvAnimal].[Name] Like '[_]%')) " 
     142              u"([djvAnimal].[Group] = 3) " 
     143              u"and ((not ([djvAnimal].[Name] Like '[_]%')) " 
    142144              u"and (not ([djvAnimal].[Name] Like '%test')))", True) 
    143145        trial(lambda x: (x.Group == '3') and (x.Date > datetime.date(2004, 2, 14)), 
     
    146148        # None values 
    147149        trial(lambda x: x.Date != None and None != x.Date, 
    148               u"([djvAnimal].[Date] Is Not Null) and ([djvAnimal].[Date] Is Not Null)", False) 
     150              u"([djvAnimal].[Date] IS NOT NULL) and ([djvAnimal].[Date] " 
     151              u"IS NOT NULL)", False) 
    149152         
    150153        # In operator 
    151154        trial(lambda x: 'tool' in x.Name or 'tool' in x.Content, 
    152               u"([djvAnimal].[Name] Like '%tool%') or ([djvAnimal].[Content] Like '%tool%')", True) 
     155              u"([djvAnimal].[Name] Like '%tool%') " 
     156              u"or ([djvAnimal].[Content] Like '%tool%')", True) 
    153157        trial(lambda x: x.Name in ('Johann', 'Gambolputty', 'de von Ausfern'), 
    154158              u"[djvAnimal].[Name] in ('Johann', 'Gambolputty', 'de von Ausfern')", True) 
     
    167171        trial(lambda x: len(x.ZipStart) == len(reqZip), u"Len([djvAnimal].[ZipStart]) = 5", False) 
    168172         
    169         # This broke on 5/10/04, because "== None" wasn't succeeding as "= Null". 
    170         trial(lambda x: x.DateTo == None, u"[djvAnimal].[DateTo] Is Null", False) 
     173        # This broke on 5/10/04, because "== None" wasn't succeeding as "= NULL". 
     174        trial(lambda x: x.DateTo == None, u"[djvAnimal].[DateTo] IS NULL", False) 
    171175         
    172176        # Another one that broke sometime in 2004. Rev 32 seems to have fixed it. 
     
    176180        trial(lambda x, y, z: x.Date == 3 and y.Qty > 4 and z.Qty < 20, 
    177181              "([djvAnimal].[Date] = 3) and (([djvAnimal].[Qty] > 4) and ([djvAnimal].[Qty] < 20))", False) 
     182         
     183        # Pickled types 
     184        e = logic.Expression(lambda x: x.Animals == [1, 2, '3']) 
     185        decom = storeado.ADOSQLDecompiler(testSM, zoo.Exhibit, e) 
     186        self.assertEqual(decom.code(), 
     187                         ("[djvExhibit].[Animals] = '(lp1\nI1\naI2\naS''3''\na.'", False)) 
    178188 
    179189 
  • trunk/storage/test_storepypgsql.py

    r42 r43  
    3838        # dirtied via __init__ is still saved. 
    3939        o = zoo.Exhibit(ID=1, Animals=[1, 2, 3]) 
     40        self.assertEqual(o.dirty(), True) 
    4041        box = zoo.arena.new_sandbox() 
    4142        box.memorize(o) 
     43        self.assertEqual(o.ID, 1) 
    4244        box.flush_all() 
    4345         
     
    8385     
    8486    def test_AdapterToPgSQL(self): 
    85         adapter = storepypgsql.AdapterToPgSQL() 
     87        adapter = storepypgsql.AdapterToPgSQL 
    8688        pairs = [(3, '3'), 
    8789                 (3.1, '3.1'), 
     
    132134        trial(lambda x: dejavu.icontains(x.Name, 'tool'), """"djvAnimal"."Name" ILIKE '%tool%'""", False) 
    133135        trial(lambda x: dejavu.icontainedby(x.Name, ('Johann', 'Gambolputty', 'de von Ausfern')), 
    134               """lower("djvAnimal"."Name") IN ('johann', 'gambolputty', 'de von ausfern')""", False) 
     136              """LOWER("djvAnimal"."Name") IN ('johann', 'gambolputty', 'de von ausfern')""", False) 
    135137        reqZip = '92104' 
    136138        trial(lambda x: len(x.ZipStart) == len(reqZip), """length("djvAnimal"."ZipStart") = 5""", False)