Author: ianb
Date: 2004-09-22 22:21:22 -0400 (Wed, 22 Sep 2004)
New Revision: 224
Added:
trunk/SQLObject/sqlobject/index.py
Modified:
trunk/SQLObject/docs/News.txt
trunk/SQLObject/docs/SQLObject.txt
trunk/SQLObject/sqlobject/__init__.py
trunk/SQLObject/sqlobject/col.py
trunk/SQLObject/sqlobject/dbconnection.py
trunk/SQLObject/sqlobject/firebird/firebirdconnection.py
trunk/SQLObject/sqlobject/main.py
trunk/SQLObject/sqlobject/maxdb/maxdbconnection.py
trunk/SQLObject/sqlobject/mysql/mysqlconnection.py
trunk/SQLObject/sqlobject/postgres/pgconnection.py
trunk/SQLObject/sqlobject/sqlite/sqliteconnection.py
trunk/SQLObject/sqlobject/sybase/sybaseconnection.py
trunk/SQLObject/tests/SQLObjectTest.py
trunk/SQLObject/tests/test.py
Log:
* Added indexing
* Documentation thereof
* Maybe made ForeignKey naming more robust (keep track of the
original name in .origName)
* Added a .module attribute to connections
* Small reorganization of tests
Modified: trunk/SQLObject/docs/News.txt
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- trunk/SQLObject/docs/News.txt 2004-09-22 22:39:54 UTC (rev 223)
+++ trunk/SQLObject/docs/News.txt 2004-09-23 02:21:22 UTC (rev 224)
@@ -15,6 +15,15 @@
=20
* Added a connection parameter to all class methods (patch 974755)
=20
+* Added indexing (from Jeremy Fitzhardinge). See `the
+ documentation`__ for more.
+
+__: SQLObject.html#indexes
+
+* Connection objects have a ``.module`` attribute, which points to
+ the DB-API module. This is useful for getting access to the
+ exception objects.
+
SQLObject 0.6
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
=20
Modified: trunk/SQLObject/docs/SQLObject.txt
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- trunk/SQLObject/docs/SQLObject.txt 2004-09-22 22:39:54 UTC (rev 223)
+++ trunk/SQLObject/docs/SQLObject.txt 2004-09-23 02:21:22 UTC (rev 224)
@@ -824,6 +824,41 @@
only give the name of the foreign class that is referenced.
`ForeignKey` implies an ``INT`` column.
=20
+Indexes
+~~~~~~~
+
+You can also define indexes for your tables, which is only meaningful
+when creating your tables through SQLObject (SQLObject relies on the
+database to implement the indexes). You do this again with attribute
+assignment, like::
+
+ firstLastIndex =3D DatabaseIndex('firstName', 'lastName')
+
+This creates an index on two columns, useful if you are selecting a
+particular name. Of course, you can give a single column, and you can
+give the column object (``firstName``) instead of the string name.
+Note that if you use ``unique`` or ``alternateID`` (which implies
+``unique``) the database may make an index for you, and primary keys
+are always indexed.
+
+If you give the keyword argument ``unique`` to `DatabaseIndex` you'll
+create a unique index -- the combination of columns must be unique.
+
+You can also use dictionaries in place of the column names, to add
+extra options. E.g.::
+
+ lastNameIndex =3D DatabaseIndex({'expression': 'lower(last_name)'})
+
+In that case, the index will be on the lower-case version of the
+column. It seems that only PostgreSQL supports this. You can also
+do::
+
+ lastNameIndex =3D DatabaseIndex({'column': lastName, 'length': 10})
+
+Which asks the database to only pay attention to the first ten
+characters. Only MySQL supports this, but it is ignored in other
+databases.
+
Creating and Dropping Tables
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
=20
@@ -1051,6 +1086,12 @@
=20
.. _kinterbasdb: http://kinterbasdb.sourceforge.net/
=20
+If you are using indexes and get an error like *key size exceeds
+implementation restriction for index*, see `this page`_ to understand
+the restrictions on your indexing.
+
+.. _this page: http://www.volny.cz/iprenosil/interbase/ip_ib_indexcalcul=
ator.htm
+
DBMConnection
-------------
=20
Modified: trunk/SQLObject/sqlobject/__init__.py
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- trunk/SQLObject/sqlobject/__init__.py 2004-09-22 22:39:54 UTC (rev 22=
3)
+++ trunk/SQLObject/sqlobject/__init__.py 2004-09-23 02:21:22 UTC (rev 22=
4)
@@ -3,6 +3,7 @@
from sqlbuilder import AND, OR, NOT, IN, LIKE, CONTAINSSTRING, const, fu=
nc
from styles import *
from joins import *
+from index import *
from include import validators
from dbconnection import connectionForURI
=20
Modified: trunk/SQLObject/sqlobject/col.py
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- trunk/SQLObject/sqlobject/col.py 2004-09-22 22:39:54 UTC (rev 223)
+++ trunk/SQLObject/sqlobject/col.py 2004-09-23 02:21:22 UTC (rev 224)
@@ -40,7 +40,8 @@
cascade=3DNone,
lazy=3DFalse,
noCache=3DFalse,
- forceDBName=3DFalse):
+ forceDBName=3DFalse,
+ origName=3DNone):
=20
# This isn't strictly true, since we *could* use backquotes or
# " or something (database-specific) around column names, but
@@ -118,6 +119,9 @@
self.validator =3D validator
self.noCache =3D noCache
self.lazy =3D lazy
+ # this is in case of ForeignKey, where we rename the column
+ # and append an ID
+ self.origName =3D origName or name
=20
def _set_validator(self, value):
self._validator =3D value
@@ -439,6 +443,7 @@
if not kw.get('name'):
kw['name'] =3D style.instanceAttrToIDAttr(style.pythonClassT=
oAttr(foreignKey))
else:
+ kw['origName'] =3D kw['name']
if not kw['name'].upper().endswith('ID'):
kw['name'] =3D style.instanceAttrToIDAttr(kw['name'])
SOKeyCol.__init__(self, **kw)
Modified: trunk/SQLObject/sqlobject/dbconnection.py
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- trunk/SQLObject/sqlobject/dbconnection.py 2004-09-22 22:39:54 UTC (re=
v 223)
+++ trunk/SQLObject/sqlobject/dbconnection.py 2004-09-23 02:21:22 UTC (re=
v 224)
@@ -326,6 +326,12 @@
def _SO_dropJoinTable(self, join):
self.query("DROP TABLE %s" % join.intermediateTable)
=20
+ def _SO_createIndex(self, soClass, index):
+ self.query(self.createIndexSQL(soClass, index))
+
+ def createIndexSQL(self, soClass, index):
+ assert 0, 'Implement in subclasses'
+
def createTable(self, soClass):
self.query(self.createTableSQL(soClass))
=20
Modified: trunk/SQLObject/sqlobject/firebird/firebirdconnection.py
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- trunk/SQLObject/sqlobject/firebird/firebirdconnection.py 2004-09-22 2=
2:39:54 UTC (rev 223)
+++ trunk/SQLObject/sqlobject/firebird/firebirdconnection.py 2004-09-23 0=
2:21:22 UTC (rev 224)
@@ -15,7 +15,8 @@
global kinterbasdb
if kinterbasdb is None:
import kinterbasdb
-
+ self.module =3D kinterbasdb
+ =20
self.limit_re =3D re.compile('^\s*(select )(.*)', re.IGNORECASE)
=20
if not autoCommit and not kw.has_key('pool'):
@@ -26,7 +27,10 @@
self.db =3D db
self.user =3D user
self.passwd =3D passwd
- self.dialect =3D int(dialect)
+ if dialect:
+ self.dialect =3D int(dialect)
+ else:
+ self.dialect =3D None
self.role =3D role
self.charset =3D charset
=20
@@ -65,14 +69,17 @@
pass
=20
def makeConnection(self):
+ extra =3D {}
+ if self.dialect:
+ extra['dialect'] =3D self.dialect
return kinterbasdb.connect(
host=3Dself.host,
database=3Dself.db,
user=3Dself.user,
password=3Dself.passwd,
- dialect=3Dself.dialect,
role=3Dself.role,
charset=3Dself.charset,
+ **extra
)
=20
def _queryInsertID(self, conn, soInstance, id, names, values):
@@ -124,6 +131,9 @@
def createIDColumn(self, soClass):
return '%s INT NOT NULL PRIMARY KEY' % soClass._idName
=20
+ def createIndexSQL(self, soClass, index):
+ return index.firebirdCreateIndexSQL(soClass)
+
def joinSQLType(self, join):
return 'INT NOT NULL'
=20
Added: trunk/SQLObject/sqlobject/index.py
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- trunk/SQLObject/sqlobject/index.py 2004-09-22 22:39:54 UTC (rev 223)
+++ trunk/SQLObject/sqlobject/index.py 2004-09-23 02:21:22 UTC (rev 224)
@@ -0,0 +1,139 @@
+from types import *
+import col
+from converters import sqlrepr
+
+class SODatabaseIndex(object):
+
+ def __init__(self,
+ soClass,
+ name,
+ columns,
+ unique=3DFalse):
+ self.soClass =3D soClass
+ self.name =3D name
+ self.descriptions =3D self.convertColumns(columns)
+ self.unique =3D unique
+
+ def convertColumns(self, columns):
+ """
+ Converts all the columns to dictionary descriptors;
+ dereferences string column names.
+ """
+ new =3D []
+ for desc in columns:
+ if not isinstance(desc, dict):
+ desc =3D {'column': desc}
+ if desc.has_key('expression'):
+ assert not desc.has_key('column'), (
+ 'You cannot provide both an expression and a column =
'
+ '(for %s in index %s in %s)' %
+ (desc, self.name, self.soClass))
+ assert not desc.has_key('length'), (
+ 'length does not apply to expressions (for %s in '
+ 'index %s in %s)' %
+ (desc, self.name, self.soClass))
+ new.append(desc)
+ continue
+ columnName =3D desc['column']
+ if not isinstance(columnName, str):
+ columnName =3D columnName.name
+ colDict =3D self.soClass._SO_columnDict
+ if not colDict.has_key(columnName):
+ for possible in colDict.values():
+ if possible.origName =3D=3D columnName:
+ column =3D 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 =3D colDict[columnName]
+ desc['column'] =3D 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 =3D 'UNIQUE INDEX'
+ else:
+ uniqueOrIndex =3D 'INDEX'
+ spec =3D []
+ for desc in self.descriptions:
+ if desc.has_key('expression'):
+ spec.append(self.getExpression(desc, 'sqlite'))
+ else:
+ spec.append(desc['column'].dbName)
+ ret =3D 'CREATE %s %s_%s ON %s (%s)' % \
+ (uniqueOrIndex,
+ self.soClass._table,
+ self.name,
+ self.soClass._table,
+ ', '.join(spec))
+ return ret
+
+ postgresCreateIndexSQL =3D maxdbCreateIndexSQL =3D sybaseCreateIndex=
SQL =3D firebirdCreateIndexSQL =3D sqliteCreateIndexSQL
+
+ def mysqlCreateIndexSQL(self, soClass):
+ if self.unique:
+ uniqueOrIndex =3D 'UNIQUE'
+ else:
+ uniqueOrIndex =3D 'INDEX'
+ spec =3D []
+ for desc in self.descriptions:
+ if desc.has_key('expression'):
+ spec.append(self.getExpression(desc, 'mysql'))
+ elif desc.has_key('length'):
+ spec.append('%s(%d)' % (desc['column'].dbName, desc['len=
gth']))
+ else:
+ spec.append(desc['column'].dbName)
+
+ return 'ALTER TABLE %s ADD %s %s (%s)' % \
+ (soClass._table, uniqueOrIndex,
+ self.name,=20
+ ', '.join(spec))
+
+
+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.
+ """
+ =20
+ baseClass =3D SODatabaseIndex
+ =20
+ def __init__(self, *columns, **kw):
+ kw['columns'] =3D columns
+ self.kw =3D kw
+
+ def setName(self, value):
+ assert self.kw.get('name') is None, "You cannot change a name af=
ter it has already been set (from %s to %s)" % (self.kw['name'], value)
+ self.kw['name'] =3D value
+
+ def withClass(self, soClass):
+ return self.baseClass(soClass=3DsoClass, **self.kw)
+
+__all__ =3D ['DatabaseIndex']
Modified: trunk/SQLObject/sqlobject/main.py
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- trunk/SQLObject/sqlobject/main.py 2004-09-22 22:39:54 UTC (rev 223)
+++ trunk/SQLObject/sqlobject/main.py 2004-09-23 02:21:22 UTC (rev 224)
@@ -28,6 +28,7 @@
import types
import warnings
import joins
+import index
import classregistry
=20
import sys
@@ -63,6 +64,7 @@
=20
implicitColumns =3D []
implicitJoins =3D []
+ implicitIndexes =3D []
for attr, value in d.items():
if isinstance(value, col.Col):
value.name =3D attr
@@ -74,6 +76,11 @@
implicitJoins.append(value)
del d[attr]
continue
+ if isinstance(value, index.DatabaseIndex):
+ value.setName(attr)
+ implicitIndexes.append(value)
+ del d[attr]
+ continue =20
=20
# We *don't* want to inherit _table, so we make sure it
# is defined in this class (not a superclass)
@@ -103,6 +110,9 @@
if not d.has_key('_joins'):
newClass._joins =3D newClass._joins[:]
newClass._joins.extend(implicitJoins)
+ if not d.has_key('_indexes'):
+ newClass._indexes =3D newClass._indexes[:]
+ newClass._indexes.extend(implicitIndexes)
=20
######################################################
# Set some attributes to their defaults, if necessary.
@@ -184,6 +194,10 @@
newClass._SO_finishedClassCreation =3D True
makeProperties(newClass)
=20
+ newClass._SO_indexList =3D []
+ for idx in newClass._indexes:
+ newClass.addIndex(idx)
+
classregistry.registry(newClass._registry).addClass(newClass)
=20
# And return the class
@@ -228,7 +242,12 @@
if d.has_key(var):
if isinstance(d[var], types.MethodType) \
or isinstance(d[var], types.FunctionType):
- warnings.warn("""I tried to set the property "%s", but i=
t was already set, as a method. Methods have significantly different sem=
antics than properties, and this may be a sign of a bug in your code.""" =
% var)
+ warnings.warn(
+ "I tried to set the property %r, but it was "
+ "already set, as a method (%r). Methods have "
+ "significantly different semantics than properties, =
"
+ "and this may be a sign of a bug in your code."
+ % (var, d[var]))
continue
setFunc(var,
property(setters.get('get'), setters.get('set'),
@@ -314,6 +333,8 @@
=20
_joins =3D []
=20
+ _indexes =3D []
+
_fromDatabase =3D False
=20
_style =3D None
@@ -357,6 +378,11 @@
=20
get =3D classmethod(get)
=20
+ def addIndex(cls, indexDef):
+ index =3D indexDef.withClass(cls)
+ cls._SO_indexList.append(index)
+ addIndex =3D classmethod(addIndex)
+
def addColumn(cls, columnDef, changeSchema=3DFalse, connection=3DNon=
e):
column =3D columnDef.withClass(cls)
name =3D column.name
@@ -432,7 +458,11 @@
else:
# Same non-caching version as above.
getter =3D eval('lambda self: self._SO_foreignKey(self._=
SO_getValue(%s), self._SO_class_%s)' % (repr(name), column.foreignKey))
- setattr(cls, rawGetterName(name)[:-2], getter)
+ if column.origName.upper().endswith('ID'):
+ origName =3D column.origName[:-2]
+ else:
+ origName =3D column.origName
+ setattr(cls, rawGetterName(origName), getter)
=20
# And we set the _get_columnName version
# (sans ID ending)
@@ -975,6 +1005,7 @@
dropTable =3D classmethod(dropTable)
=20
def createTable(cls, ifNotExists=3DFalse, createJoinTables=3DTrue,
+ createIndexes=3DTrue,
connection=3DNone):
conn =3D connection or cls._connection
if ifNotExists and conn.tableExists(cls._table):
@@ -983,6 +1014,9 @@
if createJoinTables:
cls.createJoinTables(ifNotExists=3DifNotExists,
connection=3Dconn)
+ if createIndexes:
+ cls.createIndexes(ifNotExists=3DifNotExists,
+ connection=3Dconn)
createTable =3D classmethod(createTable)
=20
def createTableSQL(cls, createJoinTables=3DTrue, connection=3DNone):
@@ -1010,6 +1044,14 @@
return '\n'.join(sql)
createJoinTablesSQL =3D classmethod(createJoinTablesSQL)
=20
+ def createIndexes(cls, ifNotExists=3DFalse, connection=3DNone):
+ conn =3D connection or cls._connection
+ for index in cls._SO_indexList:
+ if not index:
+ continue
+ conn._SO_createIndex(cls, index)
+ createIndexes =3D classmethod(createIndexes)
+
def _getJoinsToCreate(cls):
joins =3D []
for join in cls._SO_joinList:
Modified: trunk/SQLObject/sqlobject/maxdb/maxdbconnection.py
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- trunk/SQLObject/sqlobject/maxdb/maxdbconnection.py 2004-09-22 22:39:5=
4 UTC (rev 223)
+++ trunk/SQLObject/sqlobject/maxdb/maxdbconnection.py 2004-09-23 02:21:2=
2 UTC (rev 224)
@@ -65,6 +65,7 @@
global dbapi
if dbapi is None:
from sapdb import dbapi
+ self.module =3D dbapi
self.autoCommit =3D autoCommit
self.user =3D user
self.password =3D password
@@ -173,6 +174,9 @@
def createIDColumn(self, soClass):
return '%s INT PRIMARY KEY' % soClass._idName
=20
+ def createIndexSQL(self, soClass, index):
+ return index.maxdbCreateIndexSQL(soClass)
+
def dropTable(self, tableName,cascade=3DFalse):
#we drop the table in a transaction because the removal of the
#table and the sequence must be atomic=20
Modified: trunk/SQLObject/sqlobject/mysql/mysqlconnection.py
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- trunk/SQLObject/sqlobject/mysql/mysqlconnection.py 2004-09-22 22:39:5=
4 UTC (rev 223)
+++ trunk/SQLObject/sqlobject/mysql/mysqlconnection.py 2004-09-23 02:21:2=
2 UTC (rev 224)
@@ -12,6 +12,7 @@
global MySQLdb
if MySQLdb is None:
import MySQLdb
+ self.module =3D MySQLdb
self.host =3D host
self.db =3D db
self.user =3D user
@@ -67,6 +68,9 @@
def createColumn(self, soClass, col):
return col.mysqlCreateSQL()
=20
+ def createIndexSQL(self, soClass, index):
+ return index.mysqlCreateIndexSQL(soClass)
+
def createIDColumn(self, soClass):
return '%s INT PRIMARY KEY AUTO_INCREMENT' % soClass._idName
=20
Modified: trunk/SQLObject/sqlobject/postgres/pgconnection.py
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- trunk/SQLObject/sqlobject/postgres/pgconnection.py 2004-09-22 22:39:5=
4 UTC (rev 223)
+++ trunk/SQLObject/sqlobject/postgres/pgconnection.py 2004-09-23 02:21:2=
2 UTC (rev 224)
@@ -18,11 +18,11 @@
if usePygresql:
if pgdb is None:
import pgdb
- self.pgmodule =3D pgdb
+ self.module =3D pgdb
else:
if psycopg is None:
import psycopg
- self.pgmodule =3D psycopg
+ self.module =3D psycopg
=20
if dsn is None:
dsn =3D []
@@ -50,9 +50,9 @@
=20
def makeConnection(self):
try:
- conn =3D self.pgmodule.connect(self.dsn)
- except self.pgmodule.OperationalError, e:
- raise self.pgmodule.OperationalError("%s; used connection st=
ring %r" % (e, self.dsn))
+ conn =3D self.module.connect(self.dsn)
+ except self.module.OperationalError, e:
+ raise self.module.OperationalError("%s; used connection stri=
ng %r" % (e, self.dsn))
if self.autoCommit:
conn.autocommit(1)
return conn
@@ -86,6 +86,9 @@
def createColumn(self, soClass, col):
return col.postgresCreateSQL()
=20
+ def createIndexSQL(self, soClass, index):
+ return index.postgresCreateIndexSQL(soClass)
+
def createIDColumn(self, soClass):
return '%s SERIAL PRIMARY KEY' % soClass._idName
=20
Modified: trunk/SQLObject/sqlobject/sqlite/sqliteconnection.py
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- trunk/SQLObject/sqlobject/sqlite/sqliteconnection.py 2004-09-22 22:39=
:54 UTC (rev 223)
+++ trunk/SQLObject/sqlobject/sqlite/sqliteconnection.py 2004-09-23 02:21=
:22 UTC (rev 224)
@@ -11,6 +11,7 @@
global sqlite
if sqlite is None:
import sqlite
+ self.module =3D sqlite
self.filename =3D filename # full path to sqlite-db-file
if not autoCommit and not kw.has_key('pool'):
# Pooling doesn't work with transactions...
@@ -71,3 +72,6 @@
result =3D self.queryOne("SELECT tbl_name FROM sqlite_master WHE=
RE type=3D'table' AND tbl_name =3D '%s'" % tableName)
# turn it into a boolean:
return not not result
+
+ def createIndexSQL(self, soClass, index):
+ return index.sqliteCreateIndexSQL(soClass)
Modified: trunk/SQLObject/sqlobject/sybase/sybaseconnection.py
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- trunk/SQLObject/sqlobject/sybase/sybaseconnection.py 2004-09-22 22:39=
:54 UTC (rev 223)
+++ trunk/SQLObject/sqlobject/sybase/sybaseconnection.py 2004-09-23 02:21=
:22 UTC (rev 224)
@@ -17,6 +17,7 @@
from Sybase import NumericType
from sqlobject.converters import registerConverter, IntConve=
rter
registerConverter(NumericType, IntConverter)
+ self.module =3D Sybase
self.locking =3D int(locking)
self.host =3D host
self.db =3D db
@@ -104,6 +105,9 @@
def createIDColumn(self, soClass):
return '%s NUMERIC(18,0) IDENTITY UNIQUE' % soClass._idName
=20
+ def createIndexSQL(self, soClass, index):
+ return index.sybaseCreateIndexSQL(soClass)
+
def joinSQLType(self, join):
return 'NUMERIC(18,0) NOT NULL'
=20
Modified: trunk/SQLObject/tests/SQLObjectTest.py
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- trunk/SQLObject/tests/SQLObjectTest.py 2004-09-22 22:39:54 UTC (rev 2=
23)
+++ trunk/SQLObject/tests/SQLObjectTest.py 2004-09-23 02:21:22 UTC (rev 2=
24)
@@ -4,58 +4,63 @@
=20
True, False =3D 1=3D=3D1, 0=3D=3D1
=20
+def d(**kw): return kw
+
+defaultOptions =3D d(
+ # Add columns at runtime
+ supportDynamic=3DTrue,
+ # Automatically detect the columns
+ supportAuto=3DTrue,
+ # ENUM() columns that complain if you mis-assign
+ supportRestrictedEnum=3DTrue,
+ # Transcations, of course:
+ supportTransactions=3DTrue,
+ # If you can index on expressions
+ supportExpressionIndex=3DTrue,
+ )
+
def mysqlConnection():
- SQLObjectTest.supportDynamic =3D True
- SQLObjectTest.supportAuto =3D True
- # @@: MySQL *should* support this, but it appears not to
- # care when you assign incorrect to an ENUM...
- SQLObjectTest.supportRestrictedEnum =3D False
- # Technically it does, but now how we're using it:
- SQLObjectTest.supportTransactions =3D False
- return 'mysql://test@localhost/test'
+ return 'mysql://test@localhost/test', d(
+ # @@: MySQL *should* support this, but it appears not to
+ # care when you assign incorrect to an ENUM...
+ supportRestrictedEnum=3DFalse,
+ # Technically it does, but not how we're using it:
+ supportTransactions=3DFalse,
+ supportExpressionIndex=3DFalse)
=20
def dbmConnection():
- SQLObjectTest.supportDynamic =3D True
- SQLObjectTest.supportAuto =3D False
- SQLObjectTest.supportRestrictedEnum =3D False
- SQLObjectTest.supportTransactions =3D False
- return 'dbm:///data'
+ return 'dbm:///data', d(
+ supportAuto=3DFalse,
+ supportRestrictedEnum=3DFalse,
+ supportTransactions=3DFalse)
=20
def postgresConnection():
- SQLObjectTest.supportDynamic =3D True
- SQLObjectTest.supportAuto =3D True
- SQLObjectTest.supportRestrictedEnum =3D True
- SQLObjectTest.supportTransactions =3D True
- return 'postgres:///test'
+ return 'postgres:///test', d()
=20
def pygresConnection():
- SQLObjectTest.supportDynamic =3D True
- SQLObjectTest.supportAuto =3D True
- SQLObjectTest.supportRestrictedEnum =3D True
- SQLObjectTest.supportTransactions =3D True
- return 'pygresql://localhost/test'
+ return 'pygresql://localhost/test', d()
=20
def sqliteConnection():
- SQLObjectTest.supportDynamic =3D False
- SQLObjectTest.supportAuto =3D False
- SQLObjectTest.supportRestrictedEnum =3D False
SQLObjectTest.supportTransactions =3D True
- return 'sqlite:///%s/data/sqlite.data' % os.getcwd()
+ return 'sqlite:///%s/data/sqlite.data' % os.getcwd(), d(
+ supportDynamic=3DFalse,
+ supportAuto=3DFalse,
+ supportRestrictedEnum=3DFalse,
+ supportExpressionIndex=3DFalse)
=20
def sybaseConnection():
- SQLObjectTest.supportDynamic =3D False
- SQLObjectTest.supportAuto =3D False
- SQLObjectTest.supportRestrictedEnum =3D False
- SQLObjectTest.supportTransactions =3D False
- return 'sybase://test:test123@sybase/test?autoCommit=3D0'
+ return 'sybase://test:test123@sybase/test?autoCommit=3D0', d(
+ # This seems awfully pessimistic:
+ supportDynamic=3DFalse,
+ supportAuto=3DFalse,
+ supportRestrictedEnum=3DFalse)
=20
def firebirdConnection():
- SQLObjectTest.supportDynamic =3D True
- SQLObjectTest.supportAuto =3D False
- SQLObjectTest.supportRestrictedEnum =3D True
- SQLObjectTest.supportTransactions =3D True
- return 'firebird://sysdba:masterkey@localhost/var/lib/firebird/data/=
test.gdb'
+ return 'firebird://sysdba:masterkey@localhost/var/lib/firebird/data/=
test.gdb', d(
+ supportAuto=3DFalse,
+ supportExpressionIndex=3DFalse)
=20
+
_supportedDatabases =3D {
'mysql': 'MySQLdb',
'postgres': 'psycopg',
@@ -85,6 +90,14 @@
=20
databaseName =3D None
=20
+ requireSupport =3D ()
+
+ def hasSupport(self):
+ for attr in self.requireSupport:
+ if not getattr(self, attr):
+ return False
+ return True
+
def setUp(self):
global __connection__
if isinstance(__connection__, str):
@@ -93,6 +106,8 @@
print
print '#' * 70
unittest.TestCase.setUp(self)
+ if not self.hasSupport():
+ return
if self.debugInserts:
__connection__.debug =3D True
__connection__.debugOuput =3D self.debugOutput
@@ -136,6 +151,8 @@
=20
def tearDown(self):
unittest.TestCase.tearDown(self)
+ if not self.hasSupport():
+ return
__connection__.debug =3D 0
classes =3D self.classes[:]
classes.reverse()
@@ -146,7 +163,13 @@
def setDatabaseType(t):
global __connection__
try:
- conn =3D globals()[t + "Connection"]()
+ conn, ops =3D globals()[t + "Connection"]()
+ default =3D defaultOptions.copy()
+ default.update(ops)
+ ops =3D default
+ for name, value in ops.items():
+ setattr(SQLObjectTest, name, value)
+ =20
except KeyError:
raise KeyError, 'No connection by the type %s is known' % t
SQLObjectTest.databaseName =3D t
Modified: trunk/SQLObject/tests/test.py
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- trunk/SQLObject/tests/test.py 2004-09-22 22:39:54 UTC (rev 223)
+++ trunk/SQLObject/tests/test.py 2004-09-23 02:21:22 UTC (rev 224)
@@ -1137,6 +1137,62 @@
=20
=20
########################################
+## Indexes
+########################################
+
+class SOIndex1(SQLObject):
+ name =3D StringCol(length=3D100)
+ number =3D IntCol()
+
+ nameIndex =3D DatabaseIndex('name', unique=3DTrue)
+ nameIndex2 =3D DatabaseIndex(name, number)
+ nameIndex3 =3D DatabaseIndex({'column': name,
+ 'length': 3})
+
+class SOIndex2(SQLObject):
+
+ name =3D StringCol()
+
+ nameIndex =3D DatabaseIndex({'expression': 'lower(name)'})
+
+class IndexTest1(SQLObjectTest):
+
+ classes =3D [SOIndex1]
+
+ def test(self):
+ n =3D 0
+ for name in 'blah blech boring yep yort snort'.split():
+ n +=3D 1
+ SOIndex1(name=3Dname, number=3Dn)
+ mod =3D SOIndex1._connection.module
+ # Firebird doesn't throw an integrity error, unfortunately:
+ if mod.__name__.endswith('kinterbasdb'):
+ exc =3D mod.ProgrammingError
+ else:
+ exc =3D mod.IntegrityError
+ try:
+ SOIndex1(name=3D'blah', number=3D0)
+ except exc:
+ # expected
+ pass
+ else:
+ assert 0, "Exception expected."
+
+class IndexTest2(SQLObjectTest):
+
+ classes =3D [SOIndex2]
+
+ requireSupport =3D ('supportExpressionIndex',)
+ =20
+ def test(self):
+ # Not much to test, just want to make sure the table works
+ # properly
+ if not self.hasSupport():
+ return
+ SOIndex2(name=3D'')
+
+
+########################################
## Run from command-line:
########################################
=20
@@ -1254,3 +1310,4 @@
if doCoverage:
coverage.stop()
coverModules()
+
|