diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-09-30 20:17:49 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-09-30 20:17:49 -0400 |
| commit | fab9be5d0ea3e329921d6c474c4ecc618bda17ec (patch) | |
| tree | 7fa0152a1fcb07f502f323b1a371f05102e167b4 | |
| parent | 68fb10cae5069ca4408d38d546ac09d374171926 (diff) | |
| download | sqlalchemy-fab9be5d0ea3e329921d6c474c4ecc618bda17ec.tar.gz | |
- [feature] the MS Access dialect has been
moved to its own project on Bitbucket,
taking advantage of the new SQLAlchemy
dialect compliance suite. The dialect is
still in very rough shape and probably not
ready for general use yet, however
it does have *extremely* rudimental
functionality now.
| -rw-r--r-- | CHANGES | 10 | ||||
| -rw-r--r-- | doc/build/core/engines.rst | 16 | ||||
| -rw-r--r-- | doc/build/dialects/access.rst | 6 | ||||
| -rw-r--r-- | doc/build/dialects/index.rst | 3 | ||||
| -rw-r--r-- | lib/sqlalchemy/databases/__init__.py | 2 | ||||
| -rw-r--r-- | lib/sqlalchemy/dialects/__init__.py | 1 | ||||
| -rw-r--r-- | lib/sqlalchemy/dialects/access/__init__.py | 0 | ||||
| -rw-r--r-- | lib/sqlalchemy/dialects/access/base.py | 444 | ||||
| -rw-r--r-- | test/dialect/test_access.py | 31 | ||||
| -rw-r--r-- | test/requirements.py | 4 |
10 files changed, 22 insertions, 495 deletions
@@ -607,6 +607,16 @@ underneath "0.7.xx". constructed to refer to multiple remote tables is first used. [ticket:2455] +- access + - [feature] the MS Access dialect has been + moved to its own project on Bitbucket, + taking advantage of the new SQLAlchemy + dialect compliance suite. The dialect is + still in very rough shape and probably not + ready for general use yet, however + it does have *extremely* rudimental + functionality now. + - sqlite - [feature] the SQLite date and time types have been overhauled to support a more open diff --git a/doc/build/core/engines.rst b/doc/build/core/engines.rst index 13750c355..b6192ac69 100644 --- a/doc/build/core/engines.rst +++ b/doc/build/core/engines.rst @@ -46,10 +46,13 @@ Supported Databases ==================== SQLAlchemy includes many :class:`~sqlalchemy.engine.base.Dialect` implementations for various -backends; each is described as its own package in the :ref:`sqlalchemy.dialects_toplevel` package. A -SQLAlchemy dialect always requires that an appropriate DBAPI driver is installed. +backends. Dialects for the most common databases are included with SQLAlchemy; a handful +of others require an additional install of a separate dialect. -The table below summarizes the state of DBAPI support in SQLAlchemy 0.7. The values +Those dialects which are included with SQLAlchemy are described under the section :ref:`dialect_toplevel`. +All dialects additionally require that an appropriate DBAPI driver is installed. + +The table below summarizes the state of DBAPI support in this version of SQLAlchemy. The values translate as: * yes / Python platform - The SQLAlchemy dialect is mostly or fully operational on the target platform. @@ -66,7 +69,7 @@ translate as: Driver Connect string Py2K Py3K Jython Unix Windows =============================================================== =========================== =========== =========== =========== ================= ============ **DB2/Informix IDS** -ibm-db_ thirdparty thirdparty thirdparty thirdparty thirdparty thirdparty +ibm-db_ See `ibm-db`_ unknown unknown unknown unknown unknown **Drizzle** :ref:`(docs) <drizzle_toplevel>` mysql-python_ ``drizzle+mysqldb``\* yes development no yes yes **Firebird / Interbase** :ref:`(docs) <firebird_toplevel>` @@ -75,8 +78,8 @@ kinterbasdb_ ``firebird+kint informixdb_ ``informix+informixdb``\* yes development no unknown unknown **MaxDB** :ref:`(docs) <maxdb_toplevel>` sapdb_ ``maxdb+sapdb``\* development development no yes unknown -**Microsoft Access** :ref:`(docs) <access_toplevel>` -pyodbc_ ``access+pyodbc``\* development development no unknown yes +**Microsoft Access** +pyodbc_ See `sqlalchemy-access`_ development development no unknown yes **Microsoft SQL Server** :ref:`(docs) <mssql_toplevel>` adodbapi_ ``mssql+adodbapi`` development development no no yes `jTDS JDBC Driver`_ ``mssql+zxjdbc`` no no development yes yes @@ -110,6 +113,7 @@ python-sybase_ ``sybase+pysyba .. [1] The Sybase dialect currently lacks the ability to reflect tables. .. _psycopg2: http://www.initd.org/ .. _pg8000: http://pybrary.net/pg8000/ +.. _sqlalchemy-access: https://bitbucket.org/zzzeek/sqlalchemy-access .. _pypostgresql: http://python.projects.postgresql.org/ .. _mysql-python: http://sourceforge.net/projects/mysql-python .. _MySQL Connector/Python: https://launchpad.net/myconnpy diff --git a/doc/build/dialects/access.rst b/doc/build/dialects/access.rst deleted file mode 100644 index 161ec1ffd..000000000 --- a/doc/build/dialects/access.rst +++ /dev/null @@ -1,6 +0,0 @@ -.. _access_toplevel: - -Microsoft Access -================ - -.. automodule:: sqlalchemy.dialects.access.base diff --git a/doc/build/dialects/index.rst b/doc/build/dialects/index.rst index 9865d37ec..2eb770bde 100644 --- a/doc/build/dialects/index.rst +++ b/doc/build/dialects/index.rst @@ -4,7 +4,7 @@ Dialects ======== The **dialect** is the system SQLAlchemy uses to communicate with various types of DBAPIs and databases. -A compatibility chart of supported backends can be found at :ref:`supported_dbapis`. The sections that +A compatibility chart of supported backends can be found at :ref:`supported_dbapis`. The sections that follow contain reference documentation and notes specific to the usage of each backend, as well as notes for the various DBAPIs. @@ -20,7 +20,6 @@ should be consulted to check for current support level. firebird informix maxdb - access mssql mysql oracle diff --git a/lib/sqlalchemy/databases/__init__.py b/lib/sqlalchemy/databases/__init__.py index 3dae7df17..d7ce586f0 100644 --- a/lib/sqlalchemy/databases/__init__.py +++ b/lib/sqlalchemy/databases/__init__.py @@ -18,12 +18,10 @@ from ..dialects.firebird import base as firebird from ..dialects.maxdb import base as maxdb from ..dialects.informix import base as informix from ..dialects.mssql import base as mssql -from ..dialects.access import base as access from ..dialects.sybase import base as sybase __all__ = ( - 'access', 'drizzle', 'firebird', 'informix', diff --git a/lib/sqlalchemy/dialects/__init__.py b/lib/sqlalchemy/dialects/__init__.py index 69212cd6e..fa1788e78 100644 --- a/lib/sqlalchemy/dialects/__init__.py +++ b/lib/sqlalchemy/dialects/__init__.py @@ -5,7 +5,6 @@ # the MIT License: http://www.opensource.org/licenses/mit-license.php __all__ = ( -# 'access', 'drizzle', 'firebird', # 'informix', diff --git a/lib/sqlalchemy/dialects/access/__init__.py b/lib/sqlalchemy/dialects/access/__init__.py deleted file mode 100644 index e69de29bb..000000000 --- a/lib/sqlalchemy/dialects/access/__init__.py +++ /dev/null diff --git a/lib/sqlalchemy/dialects/access/base.py b/lib/sqlalchemy/dialects/access/base.py deleted file mode 100644 index 1f119098b..000000000 --- a/lib/sqlalchemy/dialects/access/base.py +++ /dev/null @@ -1,444 +0,0 @@ -# access/base.py -# Copyright (C) 2007-2011 the SQLAlchemy authors and contributors <see AUTHORS file> -# Copyright (C) 2007 Paul Johnston, paj@pajhome.org.uk -# Portions derived from jet2sql.py by Matt Keranen, mksql@yahoo.com -# -# This module is part of SQLAlchemy and is released under -# the MIT License: http://www.opensource.org/licenses/mit-license.php - -""" -Support for the Microsoft Access database. - -.. note:: - - The Access dialect is **non-functional as of SQLAlchemy 0.6**, - pending development efforts to bring it up-to-date. - - -""" -from sqlalchemy import sql, schema, types, exc, pool -from sqlalchemy.sql import compiler, expression -from sqlalchemy.engine import default, base, reflection -from sqlalchemy import processors - -class AcNumeric(types.Numeric): - def get_col_spec(self): - return "NUMERIC" - - def bind_processor(self, dialect): - return processors.to_str - - def result_processor(self, dialect, coltype): - return None - -class AcFloat(types.Float): - def get_col_spec(self): - return "FLOAT" - - def bind_processor(self, dialect): - """By converting to string, we can use Decimal types round-trip.""" - return processors.to_str - -class AcInteger(types.Integer): - def get_col_spec(self): - return "INTEGER" - -class AcTinyInteger(types.Integer): - def get_col_spec(self): - return "TINYINT" - -class AcSmallInteger(types.SmallInteger): - def get_col_spec(self): - return "SMALLINT" - -class AcDateTime(types.DateTime): - def get_col_spec(self): - return "DATETIME" - -class AcDate(types.Date): - - def get_col_spec(self): - return "DATETIME" - -class AcText(types.Text): - def get_col_spec(self): - return "MEMO" - -class AcString(types.String): - def get_col_spec(self): - return "TEXT" + (self.length and ("(%d)" % self.length) or "") - -class AcUnicode(types.Unicode): - def get_col_spec(self): - return "TEXT" + (self.length and ("(%d)" % self.length) or "") - - def bind_processor(self, dialect): - return None - - def result_processor(self, dialect, coltype): - return None - -class AcChar(types.CHAR): - def get_col_spec(self): - return "TEXT" + (self.length and ("(%d)" % self.length) or "") - -class AcBinary(types.LargeBinary): - def get_col_spec(self): - return "BINARY" - -class AcBoolean(types.Boolean): - def get_col_spec(self): - return "YESNO" - -class AcTimeStamp(types.TIMESTAMP): - def get_col_spec(self): - return "TIMESTAMP" - -class AccessExecutionContext(default.DefaultExecutionContext): - def _has_implicit_sequence(self, column): - if column.primary_key and column.autoincrement: - if isinstance(column.type, types.Integer) and \ - not column.foreign_keys: - if column.default is None or \ - (isinstance(column.default, schema.Sequence) and \ - column.default.optional): - return True - return False - - def post_exec(self): - """If we inserted into a row with a COUNTER column, fetch the ID""" - - if self.compiled.isinsert: - tbl = self.compiled.statement.table - if not hasattr(tbl, 'has_sequence'): - tbl.has_sequence = None - for column in tbl.c: - if getattr(column, 'sequence', False) or \ - self._has_implicit_sequence(column): - tbl.has_sequence = column - break - - if bool(tbl.has_sequence): - # TBD: for some reason _last_inserted_ids doesn't exist here - # (but it does at corresponding point in mssql???) - #if not len(self._last_inserted_ids) or - # self._last_inserted_ids[0] is None: - self.cursor.execute("SELECT @@identity AS lastrowid") - row = self.cursor.fetchone() - self._last_inserted_ids = [int(row[0])] - #+ self._last_inserted_ids[1:] - # print "LAST ROW ID", self._last_inserted_ids - - super(AccessExecutionContext, self).post_exec() - - -const, daoEngine = None, None -class AccessDialect(default.DefaultDialect): - colspecs = { - types.Unicode : AcUnicode, - types.Integer : AcInteger, - types.SmallInteger: AcSmallInteger, - types.Numeric : AcNumeric, - types.Float : AcFloat, - types.DateTime : AcDateTime, - types.Date : AcDate, - types.String : AcString, - types.LargeBinary : AcBinary, - types.Boolean : AcBoolean, - types.Text : AcText, - types.CHAR: AcChar, - types.TIMESTAMP: AcTimeStamp, - } - name = 'access' - supports_sane_rowcount = False - supports_sane_multi_rowcount = False - - ported_sqla_06 = False - - def type_descriptor(self, typeobj): - newobj = types.adapt_type(typeobj, self.colspecs) - return newobj - - def __init__(self, **params): - super(AccessDialect, self).__init__(**params) - self.text_as_varchar = False - self._dtbs = None - - @classmethod - def dbapi(cls): - import win32com.client, pythoncom - - global const, daoEngine - if const is None: - const = win32com.client.constants - for suffix in (".36", ".35", ".30"): - try: - daoEngine = win32com.client.\ - gencache.\ - EnsureDispatch("DAO.DBEngine" + suffix) - break - except pythoncom.com_error: - pass - else: - raise exc.InvalidRequestError( - "Can't find a DB engine. Check " - "http://support.microsoft.com/kb/239114 for details.") - - import pyodbc as module - return module - - def create_connect_args(self, url): - opts = url.translate_connect_args() - connectors = ["Driver={Microsoft Access Driver (*.mdb)}"] - connectors.append("Dbq=%s" % opts["database"]) - user = opts.get("username", None) - if user: - connectors.append("UID=%s" % user) - connectors.append("PWD=%s" % opts.get("password", "")) - return [[";".join(connectors)], {}] - - def last_inserted_ids(self): - return self.context.last_inserted_ids - - def do_execute(self, cursor, statement, params, context=None): - if params == {}: - params = () - super(AccessDialect, self).\ - do_execute(cursor, statement, params, **kwargs) - - def _execute(self, c, statement, parameters): - try: - if parameters == {}: - parameters = () - c.execute(statement, parameters) - self.context.rowcount = c.rowcount - except Exception, e: - raise exc.DBAPIError.instance(statement, parameters, e) - - def has_table(self, connection, tablename, schema=None): - # This approach seems to be more reliable that using DAO - try: - connection.execute('select top 1 * from [%s]' % tablename) - return True - except Exception, e: - return False - - def reflecttable(self, connection, table, include_columns): - # This is defined in the function, as it relies on win32com constants, - # that aren't imported until dbapi method is called - if not hasattr(self, 'ischema_names'): - self.ischema_names = { - const.dbByte: AcBinary, - const.dbInteger: AcInteger, - const.dbLong: AcInteger, - const.dbSingle: AcFloat, - const.dbDouble: AcFloat, - const.dbDate: AcDateTime, - const.dbLongBinary: AcBinary, - const.dbMemo: AcText, - const.dbBoolean: AcBoolean, - const.dbText: AcUnicode, # All Access strings are - # unicode - const.dbCurrency: AcNumeric, - } - - # A fresh DAO connection is opened for each reflection - # This is necessary, so we get the latest updates - dtbs = daoEngine.OpenDatabase(connection.engine.url.database) - - try: - for tbl in dtbs.TableDefs: - if tbl.Name.lower() == table.name.lower(): - break - else: - raise exc.NoSuchTableError(table.name) - - for col in tbl.Fields: - coltype = self.ischema_names[col.Type] - if col.Type == const.dbText: - coltype = coltype(col.Size) - - colargs = \ - { - 'nullable': not(col.Required or - col.Attributes & const.dbAutoIncrField), - } - default = col.DefaultValue - - if col.Attributes & const.dbAutoIncrField: - colargs['default'] = schema.Sequence(col.Name + '_seq') - elif default: - if col.Type == const.dbBoolean: - default = default == 'Yes' and '1' or '0' - colargs['server_default'] = \ - schema.DefaultClause(sql.text(default)) - - table.append_column( - schema.Column(col.Name, coltype, **colargs)) - - # TBD: check constraints - - # Find primary key columns first - for idx in tbl.Indexes: - if idx.Primary: - for col in idx.Fields: - thecol = table.c[col.Name] - table.primary_key.add(thecol) - if isinstance(thecol.type, AcInteger) and \ - not (thecol.default and - isinstance( - thecol.default.arg, - schema.Sequence - )): - thecol.autoincrement = False - - # Then add other indexes - for idx in tbl.Indexes: - if not idx.Primary: - if len(idx.Fields) == 1: - col = table.c[idx.Fields[0].Name] - if not col.primary_key: - col.index = True - col.unique = idx.Unique - else: - pass # TBD: multi-column indexes - - - for fk in dtbs.Relations: - if fk.ForeignTable != table.name: - continue - scols = [c.ForeignName for c in fk.Fields] - rcols = ['%s.%s' % (fk.Table, c.Name) for c in fk.Fields] - table.append_constraint( - schema.ForeignKeyConstraint(scols, rcols,\ - link_to_name=True)) - - finally: - dtbs.Close() - - @reflection.cache - def get_table_names(self, connection, schema=None, **kw): - # A fresh DAO connection is opened for each reflection - # This is necessary, so we get the latest updates - dtbs = daoEngine.OpenDatabase(connection.engine.url.database) - - names = [t.Name for t in dtbs.TableDefs - if t.Name[:4] != "MSys" and t.Name[:4] != "~TMP"] - dtbs.Close() - return names - - -class AccessCompiler(compiler.SQLCompiler): - extract_map = compiler.SQLCompiler.extract_map.copy() - extract_map.update ({ - 'month': 'm', - 'day': 'd', - 'year': 'yyyy', - 'second': 's', - 'hour': 'h', - 'doy': 'y', - 'minute': 'n', - 'quarter': 'q', - 'dow': 'w', - 'week': 'ww' - }) - - def visit_select_precolumns(self, select): - """Access puts TOP, it's version of LIMIT here """ - s = select.distinct and "DISTINCT " or "" - if select.limit: - s += "TOP %s " % (select.limit) - if select.offset: - raise exc.InvalidRequestError( - 'Access does not support LIMIT with an offset') - return s - - def limit_clause(self, select): - """Limit in access is after the select keyword""" - return "" - - def binary_operator_string(self, binary): - """Access uses "mod" instead of "%" """ - return binary.operator == '%' and 'mod' or binary.operator - - function_rewrites = {'current_date': 'now', - 'current_timestamp': 'now', - 'length': 'len', - } - def visit_function(self, func): - """Access function names differ from the ANSI SQL names; - rewrite common ones""" - func.name = self.function_rewrites.get(func.name, func.name) - return super(AccessCompiler, self).visit_function(func) - - def for_update_clause(self, select): - """FOR UPDATE is not supported by Access; silently ignore""" - return '' - - # Strip schema - def visit_table(self, table, asfrom=False, **kwargs): - if asfrom: - return self.preparer.quote(table.name, table.quote) - else: - return "" - - def visit_join(self, join, asfrom=False, **kwargs): - return (self.process(join.left, asfrom=True) + \ - (join.isouter and " LEFT OUTER JOIN " or " INNER JOIN ") + \ - self.process(join.right, asfrom=True) + " ON " + \ - self.process(join.onclause)) - - def visit_extract(self, extract, **kw): - field = self.extract_map.get(extract.field, extract.field) - return 'DATEPART("%s", %s)' % \ - (field, self.process(extract.expr, **kw)) - -class AccessDDLCompiler(compiler.DDLCompiler): - def get_column_specification(self, column, **kwargs): - colspec = self.preparer.format_column(column) + " " + \ - column.type.dialect_impl(self.dialect).get_col_spec() - - # install a sequence if we have an implicit IDENTITY column - if (not getattr(column.table, 'has_sequence', False)) and \ - column.primary_key and \ - column.autoincrement and \ - isinstance(column.type, types.Integer) and \ - not column.foreign_keys: - if column.default is None or \ - (isinstance(column.default, schema.Sequence) and - column.default.optional): - column.sequence = schema.Sequence(column.name + '_seq') - - if not column.nullable: - colspec += " NOT NULL" - - if hasattr(column, 'sequence'): - column.table.has_sequence = column - colspec = self.preparer.format_column(column) + " counter" - else: - default = self.get_column_default_string(column) - if default is not None: - colspec += " DEFAULT " + default - - return colspec - - def visit_drop_index(self, drop): - index = drop.element - self.append("\nDROP INDEX [%s].[%s]" % \ - (index.table.name, - self._index_identifier(index.name))) - -class AccessIdentifierPreparer(compiler.IdentifierPreparer): - reserved_words = compiler.RESERVED_WORDS.copy() - reserved_words.update(['value', 'text']) - def __init__(self, dialect): - super(AccessIdentifierPreparer, self).\ - __init__(dialect, initial_quote='[', final_quote=']') - - -dialect = AccessDialect -dialect.poolclass = pool.SingletonThreadPool -dialect.statement_compiler = AccessCompiler -dialect.ddlcompiler = AccessDDLCompiler -dialect.preparer = AccessIdentifierPreparer -dialect.execution_ctx_cls = AccessExecutionContext diff --git a/test/dialect/test_access.py b/test/dialect/test_access.py deleted file mode 100644 index 951d43e1a..000000000 --- a/test/dialect/test_access.py +++ /dev/null @@ -1,31 +0,0 @@ -from sqlalchemy import * -from sqlalchemy import sql -from sqlalchemy.databases import access -from sqlalchemy.testing import * - - -class CompileTest(fixtures.TestBase, AssertsCompiledSQL): - __dialect__ = access.dialect() - - def test_extract(self): - t = sql.table('t', sql.column('col1')) - - mapping = { - 'month': 'm', - 'day': 'd', - 'year': 'yyyy', - 'second': 's', - 'hour': 'h', - 'doy': 'y', - 'minute': 'n', - 'quarter': 'q', - 'dow': 'w', - 'week': 'ww' - } - - for field, subst in mapping.items(): - self.assert_compile( - select([extract(field, t.c.col1)]), - 'SELECT DATEPART("%s", t.col1) AS anon_1 FROM t' % subst) - - diff --git a/test/requirements.py b/test/requirements.py index fb347187e..b0c072898 100644 --- a/test/requirements.py +++ b/test/requirements.py @@ -193,7 +193,6 @@ class DefaultRequirements(SuiteRequirements): """Target database must support savepoints.""" return skip_if([ - "access", "sqlite", "sybase", ("mysql", "<", (5, 0, 3)), @@ -215,7 +214,7 @@ class DefaultRequirements(SuiteRequirements): @property def update_nowait(self): """Target database must support SELECT...FOR UPDATE NOWAIT""" - return skip_if(["access", "firebird", "mssql", "mysql", "sqlite", "sybase"], + return skip_if(["firebird", "mssql", "mysql", "sqlite", "sybase"], "no FOR UPDATE NOWAIT support" ) @@ -259,7 +258,6 @@ class DefaultRequirements(SuiteRequirements): """Target database must support two-phase transactions.""" return skip_if([ - no_support('access', 'two-phase xact not supported by database'), no_support('firebird', 'no SA implementation'), no_support('maxdb', 'two-phase xact not supported by database'), no_support('mssql', 'two-phase xact not supported by drivers'), |
