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

Stripped Windows carriage-returns from .py files.

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