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

1. AdapterToSQL now separates bool constants from bool expressions.
2. storage.db now has automatic connection pooling.
3. Fixed storeado against SQL Server (MSDE).
4. Added multithreading tests to zoo_fixture.

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