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

mysql: BOOLEAN -> BOOL for version < 4.1

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