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