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                    # None found
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']