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 57 (checked in by fumanchu, 8 years ago)

Fixed more mysql bool issues.

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