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