0001from types import *
0002from converters import sqlrepr
0003
0004class SODatabaseIndex(object):
0005
0006 def __init__(self,
0007 soClass,
0008 name,
0009 columns,
0010 unique=False):
0011 self.soClass = soClass
0012 self.name = name
0013 self.descriptions = self.convertColumns(columns)
0014 self.unique = unique
0015
0016 def get(self, *args, **kw):
0017 if not self.unique:
0018 raise AttributeError, (
0019 "'%s' object has no attribute 'get' (index is not unique)" % self.name)
0020 connection = kw.pop('connection', None)
0021 if args and kw:
0022 raise TypeError, "You cannot mix named and unnamed arguments"
0023 columns = [d['column'] for d in self.descriptions
0024 if d.has_key('column')]
0025 if kw and len(kw) != len(columns) or args and len(args) != len(columns):
0026 raise TypeError, ("get() takes exactly %d argument and an optional "
0027 "named argument 'connection' (%d given)" % (
0028 len(columns), len(args)+len(kw)))
0029 if args:
0030 kw = {}
0031 for i in range(len(args)):
0032 if columns[i].foreignName is not None:
0033 kw[columns[i].foreignName] = args[i]
0034 else:
0035 kw[columns[i].name] = args[i]
0036 return self.soClass.selectBy(connection=connection, **kw).getOne()
0037
0038 def convertColumns(self, columns):
0039 """
0040 Converts all the columns to dictionary descriptors;
0041 dereferences string column names.
0042 """
0043 new = []
0044 for desc in columns:
0045 if not isinstance(desc, dict):
0046 desc = {'column': desc}
0047 if desc.has_key('expression'):
0048 assert not desc.has_key('column'), (
0049 'You cannot provide both an expression and a column '
0050 '(for %s in index %s in %s)' %
0051 (desc, self.name, self.soClass))
0052 assert not desc.has_key('length'), (
0053 'length does not apply to expressions (for %s in '
0054 'index %s in %s)' %
0055 (desc, self.name, self.soClass))
0056 new.append(desc)
0057 continue
0058 columnName = desc['column']
0059 if not isinstance(columnName, str):
0060 columnName = columnName.name
0061 colDict = self.soClass.sqlmeta.columns
0062 if not colDict.has_key(columnName):
0063 for possible in colDict.values():
0064 if possible.origName == columnName:
0065 column = possible
0066 break
0067 else:
0068
0069 raise ValueError, "The column by the name %r was not found in the class %r" % (columnName, self.soClass)
0070 else:
0071 column = colDict[columnName]
0072 desc['column'] = column
0073 new.append(desc)
0074 return new
0075
0076 def getExpression(self, desc, db):
0077 if isinstance(desc['expression'], str):
0078 return desc['expression']
0079 else:
0080 return sqlrepr(desc['expression'], db)
0081
0082 def sqliteCreateIndexSQL(self, soClass):
0083 if self.unique:
0084 uniqueOrIndex = 'UNIQUE INDEX'
0085 else:
0086 uniqueOrIndex = 'INDEX'
0087 spec = []
0088 for desc in self.descriptions:
0089 if desc.has_key('expression'):
0090 spec.append(self.getExpression(desc, 'sqlite'))
0091 else:
0092 spec.append(desc['column'].dbName)
0093 ret = 'CREATE %s %s_%s ON %s (%s)' % (uniqueOrIndex,
0095 self.soClass.sqlmeta.table,
0096 self.name,
0097 self.soClass.sqlmeta.table,
0098 ', '.join(spec))
0099 return ret
0100
0101 postgresCreateIndexSQL = maxdbCreateIndexSQL = mssqlCreateIndexSQL = sybaseCreateIndexSQL = firebirdCreateIndexSQL = sqliteCreateIndexSQL
0102 def mysqlCreateIndexSQL(self, soClass):
0103 if self.unique:
0104 uniqueOrIndex = 'UNIQUE'
0105 else:
0106 uniqueOrIndex = 'INDEX'
0107 spec = []
0108 for desc in self.descriptions:
0109 if desc.has_key('expression'):
0110 spec.append(self.getExpression(desc, 'mysql'))
0111 elif desc.has_key('length'):
0112 spec.append('%s(%d)' % (desc['column'].dbName, desc['length']))
0113 else:
0114 spec.append(desc['column'].dbName)
0115
0116 return 'ALTER TABLE %s ADD %s %s (%s)' % (soClass.sqlmeta.table, uniqueOrIndex,
0118 self.name,
0119 ', '.join(spec))
0120
0121
0122class DatabaseIndex(object):
0123 """
0124 This takes a variable number of parameters, each of which is a
0125 column for indexing. Each column may be a column object or the
0126 string name of the column (*not* the database name). You may also
0127 use dictionaries, to further customize the indexing of the column.
0128 The dictionary may have certain keys:
0129
0130 'column':
0131 The column object or string identifier.
0132 'length':
0133 MySQL will only index the first N characters if this is
0134 given. For other databases this is ignored.
0135 'expression':
0136 You can create an index based on an expression, e.g.,
0137 'lower(column)'. This can either be a string or a sqlbuilder
0138 expression.
0139
0140 Further keys may be added to the column specs in the future.
0141
0142 The class also take the keyword argument `unique`; if true then
0143 a UNIQUE index is created.
0144 """
0145
0146 baseClass = SODatabaseIndex
0147
0148 def __init__(self, *columns, **kw):
0149 kw['columns'] = columns
0150 self.kw = kw
0151
0152 def setName(self, value):
0153 assert self.kw.get('name') is None, "You cannot change a name after it has already been set (from %s to %s)" % (self.kw['name'], value)
0154 self.kw['name'] = value
0155
0156 def _get_name(self):
0157 return self.kw['name']
0158
0159 def _set_name(self, value):
0160 self.setName(value)
0161
0162 name = property(_get_name, _set_name)
0163
0164 def withClass(self, soClass):
0165 return self.baseClass(soClass=soClass, **self.kw)
0166
0167 def __repr__(self):
0168 return '<%s %s %s>' % (
0169 self.__class__.__name__,
0170 hex(abs(id(self)))[2:],
0171 self.kw)
0172
0173__all__ = ['DatabaseIndex']