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

1. Unicode fixed for all SMs. db.AdapterFromDB.coerce_str now defaults to utf8 encoding. So does SM.execute().
2. decimal.Decimal tested for all SMs.

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