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