from itertools import count
from .converters import sqlrepr
creationOrder = count()
class SODatabaseIndex(object):
def __init__(self,
soClass,
name,
columns,
creationOrder,
unique=False):
self.soClass = soClass
self.name = name
self.descriptions = self.convertColumns(columns)
self.creationOrder = creationOrder
self.unique = unique
def get(self, *args, **kw):
if not self.unique:
raise AttributeError(
"'%s' object has no attribute 'get' "
"(index is not unique)" % self.name)
connection = kw.pop('connection', None)
if args and kw:
raise TypeError("You cannot mix named and unnamed arguments")
columns = [d['column'] for d in self.descriptions if 'column' in d]
if kw and len(kw) != len(columns) or \
args and len(args) != len(columns):
raise TypeError(
"get() takes exactly %d argument and an optional "
"named argument 'connection' (%d given)" % (
len(columns), len(args) + len(kw)))
if args:
kw = {}
for i in range(len(args)):
if columns[i].foreignName is not None:
kw[columns[i].foreignName] = args[i]
else:
kw[columns[i].name] = args[i]
return self.soClass.selectBy(connection=connection, **kw).getOne()
def convertColumns(self, columns):
"""
Converts all the columns to dictionary descriptors;
dereferences string column names.
"""
new = []
for desc in columns:
if not isinstance(desc, dict):
desc = {'column': desc}
if 'expression' in desc:
assert 'column' not in desc, (
'You cannot provide both an expression and a column '
'(for %s in index %s in %s)' %
(desc, self.name, self.soClass))
assert 'length' not in desc, (
'length does not apply to expressions (for %s in '
'index %s in %s)' %
(desc, self.name, self.soClass))
new.append(desc)
continue
columnName = desc['column']
if not isinstance(columnName, str):
columnName = columnName.name
colDict = self.soClass.sqlmeta.columns
if columnName not in colDict:
for possible in colDict.values():
if possible.origName == columnName:
column = possible
break
else:
# None found
raise ValueError(
"The column by the name %r was not found "
"in the class %r" % (columnName, self.soClass))
else:
column = colDict[columnName]
desc['column'] = column
new.append(desc)
return new
def getExpression(self, desc, db):
if isinstance(desc['expression'], str):
return desc['expression']
else:
return sqlrepr(desc['expression'], db)
def sqliteCreateIndexSQL(self, soClass):
if self.unique:
uniqueOrIndex = 'UNIQUE INDEX'
else:
uniqueOrIndex = 'INDEX'
spec = []
for desc in self.descriptions:
if 'expression' in desc:
spec.append(self.getExpression(desc, 'sqlite'))
else:
spec.append(desc['column'].dbName)
ret = 'CREATE %s %s_%s ON %s (%s)' % \
(uniqueOrIndex,
self.soClass.sqlmeta.table,
self.name,
self.soClass.sqlmeta.table,
', '.join(spec))
return ret
postgresCreateIndexSQL = maxdbCreateIndexSQL = mssqlCreateIndexSQL = \
sybaseCreateIndexSQL = firebirdCreateIndexSQL = sqliteCreateIndexSQL
def mysqlCreateIndexSQL(self, soClass):
if self.unique:
uniqueOrIndex = 'UNIQUE'
else:
uniqueOrIndex = 'INDEX'
spec = []
for desc in self.descriptions:
if 'expression' in desc:
spec.append(self.getExpression(desc, 'mysql'))
elif 'length' in desc:
spec.append('%s(%d)' % (desc['column'].dbName, desc['length']))
else:
spec.append(desc['column'].dbName)
return 'ALTER TABLE %s ADD %s %s (%s)' % \
(soClass.sqlmeta.table, uniqueOrIndex,
self.name,
', '.join(spec))
[docs]class DatabaseIndex(object):
"""
This takes a variable number of parameters, each of which is a
column for indexing. Each column may be a column object or the
string name of the column (*not* the database name). You may also
use dictionaries, to further customize the indexing of the column.
The dictionary may have certain keys:
'column':
The column object or string identifier.
'length':
MySQL will only index the first N characters if this is
given. For other databases this is ignored.
'expression':
You can create an index based on an expression, e.g.,
'lower(column)'. This can either be a string or a sqlbuilder
expression.
Further keys may be added to the column specs in the future.
The class also take the keyword argument `unique`; if true then
a UNIQUE index is created.
"""
baseClass = SODatabaseIndex
def __init__(self, *columns, **kw):
kw['columns'] = columns
self.kw = kw
self.creationOrder = next(creationOrder)
[docs] def setName(self, value):
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)
self.kw['name'] = value
def _get_name(self):
return self.kw['name']
def _set_name(self, value):
self.setName(value)
name = property(_get_name, _set_name)
[docs] def withClass(self, soClass):
return self.baseClass(soClass=soClass,
creationOrder=self.creationOrder, **self.kw)
def __repr__(self):
return '<%s %s %s>' % (
self.__class__.__name__,
hex(abs(id(self)))[2:],
self.kw)
__all__ = ['DatabaseIndex']