Contact: fumanchu@aminus.org

Log in as guest/dejavu to create tickets

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

root/trunk/storage/storemysql.py

Revision 51 (checked in by fumanchu, 8 years ago)

1. Changed arena.roster to ._registered_classes. Dropped containers.Prism and .Index
2. First attempt at arena.migrate (migrate class data from one store to another).
3. Fixed weakref bug in db pooling.
4. db.SM.reserve() now writes all data, not just ID.
5. Various store bugs (adapters, mostly).

Line 
1 """
2 References the MySQLdb package at:
3 http://sourceforge.net/projects/mysql-python
4
5 From the MySQL manual:
6
7 "If the server SQL mode has ANSI_QUOTES enabled, string literals can be
8 quoted only with single quotes. A string quoted with double quotes will be
9 interpreted as an identifier."
10
11 So use single quotes throughout.
12 """
13
14 # Use _mysql directly to avoid all of the DB-API overhead.
15 import _mysql
16 import datetime
17 from dejavu import storage, logic
18 from dejavu.storage import db
19
20
21 class AdapterToMySQL(db.AdapterToSQL):
22    
23     def coerce_str(self, value):
24         return "'" + _mysql.escape_string(value) + "'"
25
26
27 class AdapterFromMySQL(db.AdapterFromDB):
28    
29     def coerce_bool(self, value, coltype):
30         if isinstance(value, basestring):
31             # either '0' or '1'
32             value = (value == '1')
33         return bool(value)
34    
35     def coerce_unicode(self, value, coltype):
36         return unicode(value, "utf-8")
37
38
39 class MySQLDecompiler(db.SQLDecompiler):
40    
41     def column_name(self, name):
42         # MySQL forces lowercase column names.
43         return '%s.`%s`' % (self.tablename, name.lower())
44    
45     # --------------------------- Dispatchees --------------------------- #
46    
47     def dejavu_today(self):
48         return "CURDATE()"
49
50
51 class MySQLDecompiler411(MySQLDecompiler):
52     # Before MySQL 4.1.1, BINARY comparisons could use UPPER()
53     # or LOWER() to perform case-insensitive comparisons. Newer
54     # versions must use CONVERT() to obtain a case-sensitive
55     # encoding, like utf8.
56    
57     def dejavu_icontainedby(self, op1, op2):
58         if isinstance(op1, db.ConstWrapper):
59             # Looking for text in a field. Use Like (reverse terms).
60             return "CONVERT("+ op2 + " USING utf8) LIKE '%" + op1.strip("'\"") + "%'"
61         else:
62             # Looking for field in (a, b, c).
63             atoms = [self.adapter.coerce(x) for x in op2.basevalue]
64             return "CONVERT(%s USING utf8) IN (%s)" % (op1, ", ".join(atoms))
65    
66     def dejavu_istartswith(self, x, y):
67         y = y.strip("'\"")
68         return "CONVERT(" + x + " USING utf8) LIKE '" + y + "%'"
69    
70     def dejavu_iendswith(self, x, y):
71         y = y.strip("'\"")
72         return "CONVERT(" + x + " USING utf8) LIKE '%" + y + "'"
73    
74     def dejavu_ieq(self, x, y):
75         return "CONVERT(" + x + " USING utf8) = " + y
76
77
78 class FieldTypeAdapterMySQL(db.FieldTypeAdapter):
79     """Return the SQL typename of a DB column."""
80    
81     # This was determined through experimentation. Don't change it.
82     numeric_max_precision = 253
83    
84     def coerce_str(self, cls, key):
85         prop = getattr(cls, key)
86         bytes = int(prop.hints.get(u'bytes', '0'))
87         if bytes:
88             # MySQL VARBINARY/BLOBs will do case-sensitive comparisons.
89             # They also won't truncate trailing spaces like VARCHAR does.
90             if bytes <= 255:
91                 return u"VARBINARY(%s) CHARACTER SET utf8" % bytes
92             elif bytes < 2 ** 16:
93                 return "BLOB"
94             elif bytes < 2 ** 24:
95                 return "MEDIUMBLOB"
96         return u"LONGBLOB"
97    
98     def coerce_datetime_datetime(self, cls, key):
99         return u"DATETIME"
100
101
102 class StorageManagerMySQL(db.StorageManagerDB):
103     """StoreManager to save and retrieve Units via _mysql."""
104    
105     identifier_length = 64
106     identifier_caseless = True
107     typeAdapter = FieldTypeAdapterMySQL()
108     toAdapter = AdapterToMySQL()
109     fromAdapter = AdapterFromMySQL()
110    
111     def __init__(self, name, arena, allOptions={}):
112         db.StorageManagerDB.__init__(self, name, arena, allOptions)
113        
114         connargs = ["host", "user", "passwd", "db", "port", "unix_socket",
115                     "conv", "connect_time", "compress", "named_pipe",
116                     "init_command", "read_default_file", "read_default_group",
117                     "cursorclass", "client_flag",
118                     ]
119         self.connargs = dict([(k, v) for k, v in allOptions.iteritems()
120                               if k in connargs])
121         self.dbname = self.connargs['db']
122        
123         self.decompiler = MySQLDecompiler
124         # Try to get the version string from MySQL, to see if we need
125         # a different decompiler.
126         data, columns = self.fetch("SELECT VERSION();")
127         if data:
128             version = storage.Version(data[0][0])
129             if version > storage.Version("4.1.1"):
130                 self.decompiler = MySQLDecompiler411
131    
132     def identifier(self, *atoms):
133         # MySQL uses case-sensitive database and table names on Unix, but
134         # not on Windows. Use all-lowercase identifiers to work around the
135         # problem. "Column names, index names, and column aliases are not
136         # case sensitive on any platform."
137         # If deployers set lower_case_table_names to 1, it would help.
138         ident = ''.join(map(str, atoms)).replace('`', '``').lower()
139         idlen = self.identifier_length
140         if idlen and len(ident) > idlen:
141             warnings.warn("Identifier is longer than %s characters." % idlen)
142             ident = ident[:idlen]
143         return '`' + ident + '`'
144    
145     def _get_conn(self):
146         try:
147             conn = _mysql.connect(**self.connargs)
148         except _mysql.OperationalError, x:
149             if x.args[0] == 1040:   # Too many connections
150                 raise db.OutOfConnectionsError
151             elif x.args[0] == 1049 and self.CreateIfMissing:
152                 self.create_database()
153                 conn = _mysql.connect(**self.connargs)
154             else:
155                 raise
156         return conn
157    
158     def _template_conn(self):
159         tmplconn = self.connargs.copy()
160         tmplconn['db'] = 'mysql'
161         return _mysql.connect(**tmplconn)
162    
163     def create_database(self):
164         # _mysql has create_db and drop_db commands, but they're deprecated.
165         sql = 'CREATE DATABASE %s;' % self.identifier(self.dbname)
166         conn = self._template_conn()
167         self.execute(sql, conn)
168         conn.close()
169    
170     def drop_database(self):
171         sql = 'DROP DATABASE %s;' % self.identifier(self.dbname)
172         conn = self._template_conn()
173         self.execute(sql, conn)
174         conn.close()
175    
176     def create_storage(self, unitClass):
177         # MySQL won't allow indexes on a BLOB field without a specific length.
178         tablename = self.tablename(unitClass)
179        
180         coerce = self.typeAdapter.coerce
181         fields = []
182         for key in unitClass.properties():
183             fields.append(u'%s %s' % (self.identifier(key),
184                                       coerce(unitClass, key)))
185         self.execute(u'CREATE TABLE %s (%s);' % (tablename, ", ".join(fields)))
186        
187         for index in unitClass.indices():
188             i = self.identifier(self.prefix, "i", unitClass.__name__, index)
189            
190             dbtype = coerce(unitClass, index)
191             if dbtype.endswith('BLOB') or dbtype == 'TEXT':
192                 self.execute(u'CREATE INDEX %s ON %s (%s(%s));' %
193                              (i, tablename, self.identifier(index), 255))
194             else:
195                 self.execute(u'CREATE INDEX %s ON %s (%s);' %
196                              (i, tablename, self.identifier(index)))
197    
198     def fetch(self, query, conn=None):
199         """fetch(query, conn=None) -> rowdata, columns.
200         
201         rowdata: a nested list (or tuples), column values within rows.
202         columns: a series of 2-tuples (or more). The first tuple value
203             will be the column name, the second value will be the column
204             type.
205         """
206         if conn is None:
207             conn = self.connection()
208         self.execute(query, conn)
209         # store_result uses a client-side cursor
210         res = conn.store_result()
211         return res.fetch_row(0, 0), res.describe()
212    
213     def destroy(self, unit):
214         """destroy(unit). Delete the unit."""
215         self.execute(u'DELETE FROM %s WHERE %s = %s;' %
216                      (self.tablename(unit), self.identifier("ID"),
217                       self.toAdapter.coerce(unit.ID)))
218
Note: See TracBrowser for help on using the browser.