Index: test/testbase.py
===================================================================
--- test/testbase.py	(revision 902)
+++ test/testbase.py	(working copy)
@@ -41,6 +41,9 @@
     except:
         raise "Could not create engine.  specify --db <sqlite|sqlite_file|postgres|mysql|oracle> to test runner."
         
+    elif DBTYPE == 'mssql':
+        db_uri = 'mssql://database=test&host=127.0.0.1\\s2000&user=scott&password=tiger'
+        db = engine.create_engine(db_uri, echo=echo, default_ordering=True)
     db = EngineAssert(db)
 
 class PersistTest(unittest.TestCase):
Index: lib/sqlalchemy/databases/__init__.py
===================================================================
--- lib/sqlalchemy/databases/__init__.py	(revision 902)
+++ lib/sqlalchemy/databases/__init__.py	(working copy)
@@ -5,4 +5,4 @@
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
 
 
-__all__ = ['oracle', 'postgres', 'sqlite', 'mysql']
\ No newline at end of file
+__all__ = ['oracle', 'postgres', 'sqlite', 'mysql', 'mssql']
Index: lib/sqlalchemy/databases/mssql.py
===================================================================
--- lib/sqlalchemy/databases/mssql.py	(revision 0)
+++ lib/sqlalchemy/databases/mssql.py	(revision 0)
@@ -0,0 +1,366 @@
+# mssql.py
+# Copyright (C) 2005 Michael Bayer mike_mp@zzzcomputing.com
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+import sys, StringIO, string, types, re
+
+import sqlalchemy.sql as sql
+import sqlalchemy.engine as engine
+import sqlalchemy.schema as schema
+import sqlalchemy.ansisql as ansisql
+import sqlalchemy.types as sqltypes
+from sqlalchemy import *
+import sqlalchemy.databases.information_schema as ischema
+
+try:
+    import adodbapi as dbmodule
+    # ADODBAPI has a non-standard Connection method
+    connect = dbmodule.Connection
+    make_connect_string = lambda keys: \
+        [["Provider=SQLOLEDB;Data Source=%s;User Id=%s;Password=%s;Initial Catalog=%s" % (
+            keys["host"], keys["user"], keys["password"], keys["database"])], {}]
+    do_commit = False
+except:
+    try:
+        import pymssql as dbmodule
+        connect = dbmodule.connect
+        # pymmsql doesn't have a Binary method.  we use string
+        dbmodule.Binary = lambda st: str(st)
+        make_connect_string = lambda keys:  \
+                    [[], keys]
+        do_commit = True
+    except:
+        dbmodule = None
+        raise
+    
+class MSNumeric(sqltypes.Numeric):
+    def convert_result_value(self, value, engine):
+        return value
+
+    def convert_bind_param(self, value, engine):
+        if value is None:
+            # Not sure that this exception is needed
+            return value
+        else:
+            return str(value) 
+
+    def get_col_spec(self):
+        return "NUMERIC(%(precision)s, %(length)s)" % {'precision': self.precision, 'length' : self.length}
+
+class MSFloat(sqltypes.Float):
+    def get_col_spec(self):
+        return "FLOAT(%(precision)s)" % {'precision': self.precision}
+    def convert_bind_param(self, value, engine):
+        """By converting to string, we can use Decimal types round-trip."""
+        return str(value) 
+
+class MSInteger(sqltypes.Integer):
+    def get_col_spec(self):
+        return "INTEGER"
+
+import datetime
+class MSDateTime(sqltypes.DateTime):
+    def get_col_spec(self):
+        return "DATETIME"
+
+    def convert_bind_param(self, value, engine):
+        if hasattr(value, "isoformat"):
+            return value.isoformat(' ')
+        else:
+            return value
+
+class MSText(sqltypes.TEXT):
+    def get_col_spec(self):
+        return "TEXT"
+class MSString(sqltypes.String):
+    def get_col_spec(self):
+        return "VARCHAR(%(length)s)" % {'length' : self.length}
+class MSChar(sqltypes.CHAR):
+    def get_col_spec(self):
+        return "CHAR(%(length)s)" % {'length' : self.length}
+class MSBinary(sqltypes.Binary):
+    def get_col_spec(self):
+        return "IMAGE"
+class MSBoolean(sqltypes.Boolean):
+    def get_col_spec(self):
+        return "BIT"
+        
+colspecs = {
+    sqltypes.Integer : MSInteger,
+    sqltypes.Numeric : MSNumeric,
+    sqltypes.Float : MSFloat,
+    sqltypes.DateTime : MSDateTime,
+    sqltypes.String : MSString,
+    sqltypes.Binary : MSBinary,
+    sqltypes.Boolean : MSBoolean,
+    sqltypes.TEXT : MSText,
+    sqltypes.CHAR: MSChar,
+}
+
+ischema_names = {
+    'int' : MSInteger,
+    'varchar' : MSString,
+    'char' : MSChar,
+    'text' : MSText,
+    'decimal' : MSNumeric,
+    'numeric' : MSNumeric,
+    'float' : MSFloat,
+    'datetime' : MSDateTime,
+    'binary' : MSBinary,
+    'bit': MSBoolean,
+    'real' : MSFloat,
+    'image' : MSBinary
+}
+
+
+def engine(opts, **params):
+    return MSSQLEngine(opts, **params)
+
+def descriptor():
+    return {'name':'mssql',
+    'description':'MSSQL',
+    'arguments':[
+        ('user',"Database Username",None),
+        ('password',"Database Password",None),
+        ('db',"Database Name",None),
+        ('host',"Hostname", None),
+    ]}
+
+class MSSQLEngine(ansisql.ANSISQLEngine):
+    def __init__(self, opts, module = None, **params):
+        if module is None:
+            self.module = dbmodule
+        self.opts = opts or {}
+        ansisql.ANSISQLEngine.__init__(self, **params)
+
+    def connect_args(self):
+        return make_connect_string(self.opts)
+
+    def type_descriptor(self, typeobj):
+        return sqltypes.adapt_type(typeobj, colspecs)
+
+    def last_inserted_ids(self):
+        return self.context.last_inserted_ids
+
+    def rowid_column_name(self):
+        """returns the ROWID column name for this engine."""
+        
+        # well, for MSSQL cant really count on this being there, surprise (not).
+        # so we do some silly hack down below in MSSQLTableImpl to provide
+        # something for an OID column
+        return "rowid"
+
+    def supports_sane_rowcount(self):
+        return True
+
+    def tableimpl(self, table):
+        """returns a new sql.TableImpl object to correspond to the given Table object."""
+        return MSSQLTableImpl(table)
+
+    def compiler(self, statement, bindparams, **kwargs):
+        return MSSQLCompiler(self, statement, bindparams, **kwargs)
+
+    def schemagenerator(self, proxy, **params):
+        return MSSQLSchemaGenerator(proxy, **params)
+
+    def get_default_schema_name(self):
+        return "dbo"
+        
+    def last_inserted_ids(self):
+        return self.context.last_inserted_ids
+            
+    def do_begin(self, connection):
+        """implementations might want to put logic here for turning autocommit on/off,
+        etc."""
+        if do_commit:
+            pass  
+
+    def _execute(self, c, statement, parameters):
+        try:
+            c.execute(statement, parameters)
+            self.context.rowcount = c.rowcount
+            c.DBPROP_COMMITPRESERVE = "Y"
+        except:
+            sys.stderr.write("Issue While Running:\n"+repr(statement)+ "\nwith Parameters:\n"+ repr(parameters))
+            # Close the Parent Connection, delete it from the pool
+            del c.parent
+            raise
+
+    def do_rollback(self, connection):
+        """implementations might want to put logic here for turning autocommit on/off,
+        etc."""
+        if do_commit:
+            try:
+                # connection.rollback() for pymmsql failed sometimes--the begin tran doesn't show up
+                # this is a workaround that seems to be handle it.
+                r = self.raw_connection(connection)
+                r.query("if @@trancount > 0 rollback tran")
+                r.fetch_array()
+                r.query("begin tran")
+                r.fetch_array()
+            except:
+                pass
+        try:
+            del connection
+        except:
+            raise
+
+    def raw_connection(self, connection):
+        """Pull the raw pymmsql connection out--sensative to "pool.ConnectionFairy" and pymssql.pymssqlCnx Classes"""
+        try:
+            return connection.connection.__dict__['_pymssqlCnx__cnx']
+        except:
+            return connection.connection.adoConn
+
+    def do_commit(self, connection):
+        """implementations might want to put logic here for turning autocommit on/off, etc.
+            do_commit is set for pymmsql connections--ADO seems to handle transactions without any issue 
+        """
+        # ADO Uses Implicit Transactions.
+        if do_commit:
+            # This is very pymssql specific.  We use this instead of its commit, because it hangs on failed rollbacks.
+            # By using the "if" we don't assume an open transaction--much better.
+            r = self.raw_connection(connection)
+            r.query("if @@trancount > 0 commit tran")
+            r.fetch_array()
+            r.query("begin tran")
+            r.fetch_array()
+        else:
+            pass
+            #connection.supportsTransactions = 1
+            try:
+                pass
+                #connection.adoConn.CommitTrans()
+            except:
+                pass
+                #connection.adoConn.execute("begin trans", {})
+            #connection.adoConn.BeginTrans()
+
+    def pre_exec(self, proxy, compiled, parameters, **kwargs):
+        """
+        Special processing for making sure no identity columns have values
+        """
+        param = list(parameters)
+        if getattr(compiled, "isinsert", False):
+            for pk in compiled.statement.table.primary_key:
+                if isinstance(pk.default, schema.Sequence) and pk.name in parameters:
+                    proxy("set identity_insert %s on" % compiled.statement.table.name)
+                    self.context.turn_of_identity_insert = True
+
+    def connection(self):
+        """returns a managed DBAPI connection from this SQLEngine's connection pool."""
+        c = self._pool.connect()
+        c.supportsTransactions = 0
+        return c
+
+
+    def post_exec(self, proxy, compiled, parameters, **kwargs):
+        if getattr(compiled, "isinsert", False):
+            # table = compiled.statement.table
+            cursor = proxy("select @@identity lastrowid", {})
+            row = cursor.fetchone()
+            self.context.last_inserted_ids = [row[0]]
+            if getattr(self.context, "turn_of_identity_insert", False):
+                proxy("set identity_insert %s off" % compiled.statement.table.name)
+                self.context.turn_of_identity_insert = False
+            
+    def dbapi(self):
+        return self.module
+
+    def reflecttable(self, table):
+        ischema.reflecttable(self, table, ischema_names)
+
+        # We also run an sp_columns to check for identity columns:
+        cursor = table.engine.execute("sp_columns " + table.name, {})
+        while True:
+            row = cursor.fetchone()
+            if row is None:
+                break
+            col_name, type_name = row[3], row[5]
+            if type_name.endswith("identity"):
+                ic = table.c[col_name]
+                # Set the Default of the column to a Sequence instance
+                ic.default = schema.Sequence(ic.name + '_identity')
+
+       #cursor = table.engine.execute("sp_fkeys " + table.name, {})
+       #while True:
+       #    row = cursor.fetchone()
+       #    if row is None:
+       #        break
+
+class MSSQLTableImpl(sql.TableImpl):
+    """attached to a schema.Table to provide it with a Selectable interface
+    as well as other functions
+    """
+    def _rowid_col(self):
+        if getattr(self, '_mysql_rowid_column', None) is None:
+            if len(self.table.primary_key) > 0:
+                c = self.table.primary_key[0]
+            else:
+                c = self.table.columns[self.table.columns.keys()[0]]
+            self._mysql_rowid_column = schema.Column(c.name, c.type, hidden=True)
+            self._mysql_rowid_column._set_parent(self.table)
+
+        return self._mysql_rowid_column
+    rowid_column = property(lambda s: s._rowid_col())
+
+class MSSQLCompiler(ansisql.ANSICompiler):
+    def limit_clause(self, select):
+        # Limit in mssql is after the select keyword
+        # mssql has no support for offset
+        return ""
+    def visit_fromclause(self, fromclause):
+        self.froms[fromclause] = fromclause.from_name # + " " + fromclause.from_name
+    
+    def get_from_text(self, obj):
+        """
+        SQL Server is a little sensative about aliases.  Here we alias to table name 
+        if it's not the same as the from clause
+        """
+        if hasattr(obj, "name") and self.froms[obj] <> obj.name and not " AS " in self.froms[obj]:
+            return self.froms[obj] + " AS " + obj.name
+        else:
+            return self.froms[obj]
+
+    def get_whereclause(self, obj):
+        return self.wheres.get(obj, None)
+        
+class MSSQLSchemaGenerator(ansisql.ANSISchemaGenerator):
+    def get_column_specification(self, column, override_pk=False, first_pk=False):
+        colspec = column.name + " " + column.type.get_col_spec()
+
+        if not column.nullable:
+            colspec += " NOT NULL"
+        if column.primary_key:
+            if not override_pk:
+                colspec += " PRIMARY KEY"
+            if first_pk and isinstance(column.default, schema.Sequence):
+                colspec += " IDENTITY(1,1)"
+        if column.foreign_key:
+            colspec += " REFERENCES %s(%s)" % (column.column.foreign_key.column.table.name, column.column.foreign_key.column.name) 
+        return colspec
+
Index: lib/sqlalchemy/databases/information_schema.py
===================================================================
--- lib/sqlalchemy/databases/information_schema.py	(revision 902)
+++ lib/sqlalchemy/databases/information_schema.py	(working copy)
@@ -112,7 +112,7 @@
 #        print "row! " + repr(row)
  #       continue
         (name, type, nullable, charlen, numericprec, numericscale) = (
-            row[columns.c.column_name], 
+            row[columns.c.column_name].encode('iso-8859-1', 'replace'), 
             row[columns.c.data_type], 
             row[columns.c.is_nullable] == 'YES', 
             row[columns.c.character_maximum_length],







