From 355d4db5c07135d94883294bdc0623b97eabf1d2 Mon Sep 17 00:00:00 2001
From: David Thompson <dthompson@member.fsf.org>
Date: Wed, 11 Apr 2012 12:46:12 -0400
Subject: [PATCH 1/3] Added experimental MySQL support. Migration from MongoDB
is untested but starting anew with MySQL works.
---
mediagoblin/db/sql/extratypes.py | 34 +++++++++++++++++++++++++++++++---
mediagoblin/db/sql/models.py | 25 ++++++++++++++++---------
mediagoblin/db/sql/models_v0.py | 12 ++++++------
3 files changed, 53 insertions(+), 18 deletions(-)
diff --git a/mediagoblin/db/sql/extratypes.py b/mediagoblin/db/sql/extratypes.py
index 8e078f1..d7249c3 100644
a
|
b
|
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 | 17 | |
18 | | from sqlalchemy.types import TypeDecorator, Unicode, VARCHAR |
| 18 | from sqlalchemy.types import TypeDecorator, Unicode, VARCHAR, TEXT |
19 | 19 | import json |
20 | 20 | |
21 | 21 | |
22 | 22 | class PathTupleWithSlashes(TypeDecorator): |
23 | 23 | "Represents a Tuple of strings as a slash separated string." |
24 | 24 | |
25 | | impl = Unicode |
| 25 | impl = Unicode().with_variant(TEXT(), 'mysql') |
26 | 26 | |
27 | 27 | def process_bind_param(self, value, dialect): |
28 | 28 | if value is not None: |
… |
… |
class PathTupleWithSlashes(TypeDecorator):
|
50 | 50 | class JSONEncoded(TypeDecorator): |
51 | 51 | "Represents an immutable structure as a json-encoded string." |
52 | 52 | |
53 | | impl = VARCHAR |
| 53 | impl = VARCHAR().with_variant(TEXT(), 'mysql') |
54 | 54 | |
55 | 55 | def process_bind_param(self, value, dialect): |
56 | 56 | if value is not None: |
… |
… |
class JSONEncoded(TypeDecorator):
|
61 | 61 | if value is not None: |
62 | 62 | value = json.loads(value) |
63 | 63 | return value |
| 64 | |
| 65 | class Unicode(TypeDecorator): |
| 66 | """ |
| 67 | Represents a string of unicode characters. |
| 68 | |
| 69 | SQLite and PostgreSQL do not require a length to be specified for |
| 70 | VARCHAR types. However, MySQL does require it. Thus, we need a |
| 71 | Unicode class that includes a with_variant decorator to use a TEXT |
| 72 | type for MySQL. |
| 73 | """ |
| 74 | |
| 75 | impl = VARCHAR().with_variant(TEXT(), 'mysql') |
| 76 | |
| 77 | class UnicodeKey(TypeDecorator): |
| 78 | """ |
| 79 | Represents a string of unicode characters, with a MySQL variant |
| 80 | that limits the length to 255. |
| 81 | |
| 82 | Database keys to have a set length. The Unicode class falls |
| 83 | back to TEXT for MySQL, but TEXT cannot be used for keys. So, a |
| 84 | VARCHAR of length 255 is used instead. |
| 85 | """ |
| 86 | |
| 87 | impl = VARCHAR().with_variant(VARCHAR(length=255), 'mysql') |
| 88 | |
| 89 | def unicodeOptLen(length=255): |
| 90 | return Unicode().with_variant(VARCHAR(length=length), 'mysql') |
| 91 | |
diff --git a/mediagoblin/db/sql/models.py b/mediagoblin/db/sql/models.py
index e87aadd..d6cd209 100644
a
|
b
|
import datetime
|
23 | 23 | import sys |
24 | 24 | |
25 | 25 | from sqlalchemy import ( |
26 | | Column, Integer, Unicode, UnicodeText, DateTime, Boolean, ForeignKey, |
| 26 | Column, Integer, UnicodeText, DateTime, Boolean, ForeignKey, |
27 | 27 | UniqueConstraint, PrimaryKeyConstraint, SmallInteger) |
28 | 28 | from sqlalchemy.orm import relationship |
29 | 29 | from sqlalchemy.orm.collections import attribute_mapped_collection |
… |
… |
from sqlalchemy.sql.expression import desc
|
31 | 31 | from sqlalchemy.ext.associationproxy import association_proxy |
32 | 32 | from sqlalchemy.util import memoized_property |
33 | 33 | |
34 | | from mediagoblin.db.sql.extratypes import PathTupleWithSlashes, JSONEncoded |
| 34 | from sqlalchemy.dialects import mysql |
| 35 | |
| 36 | from mediagoblin.db.sql.extratypes import PathTupleWithSlashes, JSONEncoded, Unicode, UnicodeKey |
35 | 37 | from mediagoblin.db.sql.base import Base, DictReadAttrProxy |
36 | 38 | from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, MediaCommentMixin |
37 | 39 | from mediagoblin.db.sql.base import Session |
… |
… |
from mediagoblin.db.sql.base import Session
|
43 | 45 | # this import-based meddling... |
44 | 46 | from migrate import changeset |
45 | 47 | |
46 | | |
47 | 48 | class SimpleFieldAlias(object): |
48 | 49 | """An alias for any field""" |
49 | 50 | def __init__(self, fieldname): |
… |
… |
class User(Base, UserMixin):
|
64 | 65 | __tablename__ = "core__users" |
65 | 66 | |
66 | 67 | id = Column(Integer, primary_key=True) |
67 | | username = Column(Unicode, nullable=False, unique=True) |
| 68 | username = Column(UnicodeKey, nullable=False, unique=True) |
68 | 69 | email = Column(Unicode, nullable=False) |
69 | 70 | created = Column(DateTime, nullable=False, default=datetime.datetime.now) |
70 | 71 | pw_hash = Column(Unicode, nullable=False) |
… |
… |
class MediaEntry(Base, MediaEntryMixin):
|
92 | 93 | id = Column(Integer, primary_key=True) |
93 | 94 | uploader = Column(Integer, ForeignKey(User.id), nullable=False, index=True) |
94 | 95 | title = Column(Unicode, nullable=False) |
95 | | slug = Column(Unicode) |
| 96 | slug = Column(UnicodeKey) |
96 | 97 | created = Column(DateTime, nullable=False, default=datetime.datetime.now, |
97 | 98 | index=True) |
98 | 99 | description = Column(UnicodeText) # ?? |
… |
… |
class FileKeynames(Base):
|
214 | 215 | """ |
215 | 216 | __tablename__ = "core__file_keynames" |
216 | 217 | id = Column(Integer, primary_key=True) |
217 | | name = Column(Unicode, unique=True) |
| 218 | name = Column(UnicodeKey, unique=True) |
218 | 219 | |
219 | 220 | def __repr__(self): |
220 | 221 | return "<FileKeyname %r: %r>" % (self.id, self.name) |
… |
… |
class MediaFile(Base):
|
234 | 235 | """ |
235 | 236 | __tablename__ = "core__mediafiles" |
236 | 237 | |
| 238 | # Use SmallInteger for name_id, but add a variant for MySQL to use |
| 239 | # a regular INTEGER or else the table creation will fail. MySQL |
| 240 | # seems to expect that the FOREIGN KEY type matches the type that |
| 241 | # it references. |
| 242 | smallint = SmallInteger().with_variant(mysql.INTEGER(), 'mysql') |
| 243 | |
237 | 244 | media_entry = Column( |
238 | 245 | Integer, ForeignKey(MediaEntry.id), |
239 | 246 | nullable=False) |
240 | | name_id = Column(SmallInteger, ForeignKey(FileKeynames.id), nullable=False) |
| 247 | name_id = Column(smallint, ForeignKey(FileKeynames.id), nullable=False) |
241 | 248 | file_path = Column(PathTupleWithSlashes) |
242 | 249 | |
243 | 250 | __table_args__ = ( |
… |
… |
class Tag(Base):
|
274 | 281 | __tablename__ = "core__tags" |
275 | 282 | |
276 | 283 | id = Column(Integer, primary_key=True) |
277 | | slug = Column(Unicode, nullable=False, unique=True) |
| 284 | slug = Column(UnicodeKey, nullable=False, unique=True) |
278 | 285 | |
279 | 286 | def __repr__(self): |
280 | 287 | return "<Tag %r: %r>" % (self.id, self.slug) |
… |
… |
MODELS = [
|
350 | 357 | class MigrationData(Base): |
351 | 358 | __tablename__ = "core__migrations" |
352 | 359 | |
353 | | name = Column(Unicode, primary_key=True) |
| 360 | name = Column(UnicodeKey, primary_key=True) |
354 | 361 | version = Column(Integer, nullable=False, default=0) |
355 | 362 | |
356 | 363 | ###################################################### |
diff --git a/mediagoblin/db/sql/models_v0.py b/mediagoblin/db/sql/models_v0.py
index 5dd6b38..47e5ece 100644
a
|
b
|
import datetime
|
23 | 23 | import sys |
24 | 24 | |
25 | 25 | from sqlalchemy import ( |
26 | | Column, Integer, Unicode, UnicodeText, DateTime, Boolean, ForeignKey, |
| 26 | Column, Integer, UnicodeText, DateTime, Boolean, ForeignKey, |
27 | 27 | UniqueConstraint, PrimaryKeyConstraint, SmallInteger, Float) |
28 | 28 | from sqlalchemy.ext.declarative import declarative_base |
29 | 29 | from sqlalchemy.orm import relationship, backref |
… |
… |
from sqlalchemy.orm.collections import attribute_mapped_collection
|
31 | 31 | from sqlalchemy.ext.associationproxy import association_proxy |
32 | 32 | from sqlalchemy.util import memoized_property |
33 | 33 | |
34 | | from mediagoblin.db.sql.extratypes import PathTupleWithSlashes, JSONEncoded |
| 34 | from mediagoblin.db.sql.extratypes import PathTupleWithSlashes, JSONEncoded, Unicode, UnicodeKey |
35 | 35 | from mediagoblin.db.sql.base import GMGTableBase |
36 | 36 | from mediagoblin.db.sql.base import Session |
37 | 37 | |
… |
… |
class User(Base_v0):
|
47 | 47 | __tablename__ = "core__users" |
48 | 48 | |
49 | 49 | id = Column(Integer, primary_key=True) |
50 | | username = Column(Unicode, nullable=False, unique=True) |
| 50 | username = Column(UnicodeKey, nullable=False, unique=True) |
51 | 51 | email = Column(Unicode, nullable=False) |
52 | 52 | created = Column(DateTime, nullable=False, default=datetime.datetime.now) |
53 | 53 | pw_hash = Column(Unicode, nullable=False) |
… |
… |
class FileKeynames(Base_v0):
|
144 | 144 | """ |
145 | 145 | __tablename__ = "core__file_keynames" |
146 | 146 | id = Column(Integer, primary_key=True) |
147 | | name = Column(Unicode, unique=True) |
| 147 | name = Column(UnicodeKey, unique=True) |
148 | 148 | |
149 | 149 | def __repr__(self): |
150 | 150 | return "<FileKeyname %r: %r>" % (self.id, self.name) |
… |
… |
class Tag(Base_v0):
|
199 | 199 | __tablename__ = "core__tags" |
200 | 200 | |
201 | 201 | id = Column(Integer, primary_key=True) |
202 | | slug = Column(Unicode, nullable=False, unique=True) |
| 202 | slug = Column(UnicodeKey, nullable=False, unique=True) |
203 | 203 | |
204 | 204 | def __repr__(self): |
205 | 205 | return "<Tag %r: %r>" % (self.id, self.slug) |
… |
… |
class VideoData(Base_v0):
|
294 | 294 | class MigrationData(Base_v0): |
295 | 295 | __tablename__ = "core__migrations" |
296 | 296 | |
297 | | name = Column(Unicode, primary_key=True) |
| 297 | name = Column(UnicodeKey, primary_key=True) |
298 | 298 | version = Column(Integer, nullable=False, default=0) |
299 | 299 | |
300 | 300 | ###################################################### |
--
1.7.10.4
From 23ae959996afe0d1d816e05db4e77cb391dadfcb Mon Sep 17 00:00:00 2001
From: David Thompson <dthompson@member.fsf.org>
Date: Wed, 11 Apr 2012 12:53:48 -0400
Subject: [PATCH 2/3] Left some changes out of last commit. Updating
extratypes.py
---
mediagoblin/db/sql/extratypes.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/mediagoblin/db/sql/extratypes.py b/mediagoblin/db/sql/extratypes.py
index d7249c3..7f563c3 100644
a
|
b
|
class UnicodeKey(TypeDecorator):
|
87 | 87 | impl = VARCHAR().with_variant(VARCHAR(length=255), 'mysql') |
88 | 88 | |
89 | 89 | def unicodeOptLen(length=255): |
| 90 | """ |
| 91 | Returns a Unicode instance with a MySQL variant of VARCHAR with a |
| 92 | specific length. |
| 93 | """ |
| 94 | |
90 | 95 | return Unicode().with_variant(VARCHAR(length=length), 'mysql') |
91 | 96 | |