1
# -*- test-case-name: axiom.test -*-
5
from twisted.python.filepath import FilePath
7
from axiom.slotmachine import Attribute as inmemory
8
from axiom.extime import Time
10
_NEEDS_FETCH = object() # token indicating that a value was not found
12
class SQLAttribute(inmemory):
14
Abstract superclass of all attributes.
16
_Not_ an attribute itself.
18
def coercer(self, value):
20
must return a value equivalent to the data being passed in for it to be
21
considered valid for a value of this attribute. for example, 'int' or
25
raise NotImplementedError()
29
def __init__(self, doc='', indexed=False):
30
inmemory.__init__(self, doc)
31
self.indexed = indexed
34
def infilter(self, pyval, oself):
36
used to convert a Python value to something that lives in the database;
37
so called because it is called when objects go in to the database. It
38
takes a Python value and returns an SQL value.
40
raise NotImplementedError()
42
def outfilter(self, dbval, oself):
44
used to convert an SQL value to something that lives in memory; so
45
called because it is called when objects come out of the database. It
46
takes an SQL value and returns a Python value.
50
# requiredSlots must be called before it's run
52
prefix = "_atop_memory_"
53
dbprefix = "_atop_store_"
55
def requiredSlots(self, modname, classname, attrname):
56
self.modname = modname
57
self.classname = classname
58
self.attrname = attrname
59
self.columnName = attrname
60
self.underlying = self.prefix + attrname
61
self.dbunderlying = self.dbprefix + attrname
63
yield self.dbunderlying
65
def fullyQualifiedName(self):
66
return '.'.join([self.modname,
70
def __get__(self, oself, type=None):
71
if type is not None and oself is None:
72
return ColumnComparer(self, type)
74
pyval = getattr(oself, self.underlying, _NEEDS_FETCH)
75
st = getattr(oself, 'store')
76
if pyval is _NEEDS_FETCH:
77
dbval = getattr(oself, self.dbunderlying, _NEEDS_FETCH)
78
if dbval is _NEEDS_FETCH:
79
raise AttributeError(self.attrname)
81
pyval = self.outfilter(dbval, oself)
82
setattr(oself, self.underlying, pyval)
85
def loaded(self, oself, dbval):
86
setattr(oself, self.dbunderlying, dbval)
87
delattr(oself, self.underlying) # member_descriptors don't raise
88
# attribute errors; what gives? good
91
def __set__(self, oself, pyval):
92
# convert to dbval later, I guess?
94
dbval = self.infilter(pyval, oself)
95
oself.__dirty__[self.attrname] = dbval
97
setattr(oself, self.underlying, pyval)
98
if st is not None and st.autocommit:
101
class ColumnComparer:
102
def __init__(self, atr, typ):
105
self.compared = False # not yet activated
106
self.otherComp = None
108
def compare(self, other, sqlop):
109
assert not self.compared # activation time
111
# interim: maybe we want objects later? right now strings should be fine
112
if isinstance(other, ColumnComparer):
113
self.otherComp = other
114
self.sql = ('(%s.%s %s %s.%s)' % (self.type.getTableName(),
115
self.attribute.columnName,
117
other.type.getTableName(),
118
other.attribute.columnName))
121
# convert to constant usable in the database
122
self.sql = ('(%s.%s %s ?)' % (self.type.getTableName(),
123
self.attribute.columnName,
125
self.preArgs = [other]
129
def __eq__(self, other):
130
return self.compare(other, '=')
131
def __ne__(self, other):
132
return self.compare(other, '!=')
133
def __gt__(self, other):
134
return self.compare(other, '>')
135
def __lt__(self, other):
136
return self.compare(other, '<')
137
def __ge__(self, other):
138
return self.compare(other, '>=')
139
def __le__(self, other):
140
return self.compare(other, '<=')
142
# XXX TODO: improve error reporting
146
def getArgsFor(self, store):
147
return [self.attribute.infilter(arg, None) for arg in self.preArgs]
149
def getTableNames(self):
150
names = [self.type.getTableName()]
151
if self.otherComp is not None:
152
names.append(self.otherComp.type.getTableName())
156
return 'ORDER BY %s.%s ASC' % (self.type.getTableName(),
157
self.attribute.columnName)
160
return 'ORDER BY %s.%s DESC' % (self.type.getTableName(),
161
self.attribute.columnName)
163
descending = property(_desc)
164
ascending = property(_asc)
169
def __init__(self, a, b):
174
return ' '.join(['(',
176
self.__class__.__name__,
180
def getArgsFor(self, store):
181
return self.a.getArgsFor(store) + self.b.getArgsFor(store)
183
def getTableNames(self):
184
return (self.a.getTableNames() + self.b.getTableNames())
189
class integer(SQLAttribute):
191
def infilter(self, pyval, oself):
196
class bytes(SQLAttribute):
198
Attribute representing a sequence of bytes; this is represented in memory
204
def infilter(self, pyval, oself):
207
def outfilter(self, dbval, oself):
212
class InvalidPathError(ValueError):
214
A path that could not be used with the database was attempted to be used
218
class text(SQLAttribute):
220
Attribute representing a sequence of characters; this is represented in
221
memory as a Python 'unicode'.
224
def __init__(self, doc='', indexed=False, caseSensitive=False):
225
SQLAttribute.__init__(self, doc, indexed)
227
self.sqltype = 'TEXT'
229
self.sqltype = 'TEXT COLLATE NOCASE'
230
self.caseSensitive = caseSensitive
232
def infilter(self, pyval, oself):
233
if not isinstance(pyval, unicode) or '\x00' in pyval:
234
raise TypeError("text must be (unicode string without NULL bytes); not %r" %
235
(type(pyval).__name__,))
238
def outfilter(self, dbval, oself):
243
def infilter(self, pyval, oself):
244
mypath = unicode(pyval.path)
245
storepath = oself.store.filesdir
246
if not mypath.startswith(storepath):
247
raise InvalidPathError('%s not in %s' % (mypath, storepath))
248
p = mypath[len(storepath):]
249
if p.startswith('/'):
251
return super(path, self).infilter(p, oself)
253
def outfilter(self, dbval, oself):
256
fp = FilePath(os.path.join(oself.store.filesdir, dbval))
262
class timestamp(integer):
264
def infilter(self, pyval, oself):
267
return int(pyval.asPOSIXTimestamp() * MICRO)
269
def outfilter(self, dbval, oself):
272
return Time.fromPOSIXTimestamp(dbval / MICRO)
274
class reference(integer):
275
def __init__(self, doc='', indexed=True, reftype=None):
276
integer.__init__(self, doc, indexed)
277
self.reftype = reftype
279
def infilter(self, pyval, oself):
284
if oself.store != pyval.store:
285
raise AttributeError(
286
"You can't establish references to items in other stores.")
287
if oself.store is None:
288
raise AttributeError(
289
"TODO: Setting references on items outside of stores is "
290
"currently unsupported. Set .store first.")
293
def outfilter(self, dbval, oself):
296
return oself.store.getItemByID(dbval, autoUpgrade=not oself.__legacy__)