diff --git a/COPYING.txt b/COPYING similarity index 100% rename from COPYING.txt rename to COPYING diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index c8a5f6c..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1 +0,0 @@ -include COPYING.txt diff --git a/README.md b/README.md deleted file mode 100644 index c64eef9..0000000 --- a/README.md +++ /dev/null @@ -1,43 +0,0 @@ - -# SQLBase7-SA - -SQLBase7-SA is a SQLAlchemy driver/dialect for the Centura SQLBase -database, specifically version 7.5.1. - -## About the Project - -From what I can tell, SQLBase is still an actively-developed database, -but it is no longer owned by Centura (see -[here](http://en.wikipedia.org/wiki/Gupta_Technologies)). Also, the -current version (according to [this -page](http://www.unify.com/Products/Data_Management/SQLBase/), as of -25 Apr 2010) is 11.5, so I have no idea how useful this project will -be for versions of SQLBase more recent than 7.5.1. - -This project exists only for the sake of providing read-only access to -legacy data, specifically that used by the -[CAM32](http://www.camcommerce.com/products/CAM32.aspx) Point of Sale -software. It's possible that it could allow writing data, etc., but I -personally won't be adding any such features unless/until the need -arises. - -I don't expect there to be much of anyone using SQLBase 7.5.1 at this -point (besides perhaps other CAM32 users), but if you do happen to -need additional functionality from this project or just have questions -or comments, feel free to drop me a line at lance@edbob.org. - -## Downloads - -The code is released under the [GNU General Public -License](http://www.gnu.org/licenses/gpl.html), version 3. - -It is available at [PyPI](http://pypi.python.org/pypi/SQLBase7-SA), so -the easiest way to get the package is with the command: - - # pip install SQLBase7-SA - -Again, this project is extremely specific to my needs, so I'm only -building eggs for Python 2.5 and 2.6 at this point. If you happen to -need something else then please contact me. - -Copyright © 2010 Lance Edgar diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 9c7b914..0000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[egg_info] -tag_build = .dev diff --git a/setup.py b/setup.py index 75395a7..c2bd74d 100644 --- a/setup.py +++ b/setup.py @@ -1,86 +1,53 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -################################################################################ -# -# SQLBase7-SA -- SQLAlchemy driver/dialect for Centura SQLBase v7 -# Copyright © 2010 Lance Edgar -# -# This file is part of SQLBase7-SA. -# -# SQLBase7-SA is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# SQLBase7-SA 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with SQLBase7-SA. If not, see . -# -################################################################################ - - -from setuptools import setup, find_packages - - -import os -execfile(os.path.join(os.path.dirname(__file__), 'sqlbase7_sa', '_version.py')) - - -setup( - name = 'SQLBase7-SA', - version = __version__, - author = 'Lance Edgar', - author_email = 'lance@edbob.org', - url = "https://forgejo.wuttaproject.org/rattail/sqlbase7-sa", - license = "GNU GPL v3", - description = 'SQLAlchemy dialect for Centura SQLBase v7', - long_description = """ -SQLBase7-SA - SQLAlchemy dialect for Centura SQLBase v7 -------------------------------------------------------- - -This package provides a (possibly rudimentary) implementation -of a SQLAlchemy dialect for the Centura SQLBase database -engine. It is only intended (and known) to work with a very -specific version of this database, that version being 7.5.1. -""", - - classifiers = [ - 'Development Status :: 4 - Beta', - 'Environment :: Plugins', - 'Environment :: Win32 (MS Windows)', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: GNU General Public License (GPL)', - 'Natural Language :: English', - 'Operating System :: Microsoft :: Windows', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2.5', - 'Programming Language :: Python :: 2.6', - 'Topic :: Database', - 'Topic :: Software Development :: Libraries :: Python Modules', - ], - - packages = find_packages(), - - install_requires = [ - 'SQLAlchemy>0.5.2', - ], - - entry_points = { - - # SQLAlchemy 0.5 - 'sqlalchemy.databases' : [ - 'sqlbase7 = sqlbase7_sa.sqlbase7_sa05:SQLBase7Dialect_SA05', - ], - - # SQLAlchemy 0.6 - 'sqlalchemy.dialects' : [ - 'sqlbase7 = sqlbase7_sa.sqlbase7_sa06:SQLBase7Dialect_SA06_pyodbc', - ], - }, - - test_suite = 'sqlbase7_sa.tests', - ) +#!/usr/bin/env python +# -*- coding: utf-8 -*- +################################################################################ +# +# SQLBase7-SA -- SQLAlchemy driver/dialect for Centura SQLBase v7 +# Copyright © 2010 Lance Edgar +# +# This file is part of SQLBase7-SA. +# +# SQLBase7-SA is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SQLBase7-SA 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SQLBase7-SA. If not, see . +# +################################################################################ + + +from setuptools import setup, find_packages + + +import os +execfile(os.path.join(os.path.dirname(__file__), 'sqlbase7_sa', '_version.py')) + + +setup( + name = 'SQLBase7-SA', + version = __version__, + description = 'SQLAlchemy driver/dialect for Centura SQLBase v7', + author = 'Lance Edgar', + author_email = 'lance@edbob.org', + + packages = find_packages(), + + install_requires = [ + 'SQLAlchemy>=0.6.0', + ], + + entry_points = { + 'sqlalchemy.dialects' : [ + 'sqlbase7 = sqlbase7_sa:base.dialect', + ], + }, + + test_suite = 'sqlbase7_sa.tests', + ) diff --git a/sqlbase7_sa/__init__.py b/sqlbase7_sa/__init__.py index a7c7be0..96991e6 100644 --- a/sqlbase7_sa/__init__.py +++ b/sqlbase7_sa/__init__.py @@ -23,4 +23,11 @@ ################################################################################ +from sqlbase7_sa import base +from sqlbase7_sa import pyodbc + from sqlbase7_sa._version import __version__ + + +# default dialect +base.dialect = pyodbc.dialect diff --git a/sqlbase7_sa/_version.py b/sqlbase7_sa/_version.py index 5bb4149..5f74cfc 100644 --- a/sqlbase7_sa/_version.py +++ b/sqlbase7_sa/_version.py @@ -23,4 +23,4 @@ ################################################################################ -__version__ = '0.1b5' +__version__ = '0.1a1' diff --git a/sqlbase7_sa/sqlbase7.py b/sqlbase7_sa/base.py similarity index 51% rename from sqlbase7_sa/sqlbase7.py rename to sqlbase7_sa/base.py index 690395c..be7dfd1 100644 --- a/sqlbase7_sa/sqlbase7.py +++ b/sqlbase7_sa/base.py @@ -25,16 +25,8 @@ from sqlalchemy.engine.default import DefaultDialect from sqlalchemy import types, and_ +from sqlalchemy.sql.compiler import SQLCompiler from sqlalchemy.sql.expression import Join -from sqlalchemy.sql import visitors, operators, ClauseElement - - -import sqlalchemy -from pkg_resources import parse_version -if parse_version(sqlalchemy.__version__) <= parse_version('0.5.99'): - from sqlalchemy.sql.compiler import DefaultCompiler as CompilerBase -else: - from sqlalchemy.sql.compiler import SQLCompiler as CompilerBase class LimitClauseNotSupported(Exception): @@ -47,7 +39,7 @@ class LimitClauseNotSupported(Exception): return "Centura SQLBase 7.5.1 doesn't support a LIMIT clause for the SELECT statement (received: limit = %u, offset = %u)" % (self.limit, self.offset) -class SQLBase7Compiler(CompilerBase): +class SQLBase7Compiler(SQLCompiler): # Most of the code below was copied from the Oracle dialect. Thanks to Michael Bayer # for pointing that out. Oh, and for writing SQLAlchemy; that was pretty cool. @@ -57,12 +49,7 @@ class SQLBase7Compiler(CompilerBase): return self.process(join.left, **kwargs) + ", " + self.process(join.right, **kwargs) def visit_select(self, select, **kwargs): - if self.stack and 'from' in self.stack[-1]: - existingfroms = self.stack[-1]['from'] - else: - existingfroms = None - - froms = select._get_display_froms(existingfroms) + froms = select._get_display_froms() whereclause = self._get_join_whereclause(froms) if whereclause is not None: select = select.where(whereclause) @@ -71,22 +58,13 @@ class SQLBase7Compiler(CompilerBase): raise LimitClauseNotSupported(select._limit, select._offset) kwargs['iswrapper'] = getattr(select, '_is_wrapper', False) - return super(SQLBase7Compiler, self).visit_select(select, **kwargs) + return SQLCompiler.visit_select(self, select, **kwargs) def _get_join_whereclause(self, froms): clauses = [] def visit_join(join): - if join.isouter: - def visit_binary(binary): - if binary.operator == operators.eq: - if binary.left.table is join.right: - binary.left = _OuterJoinColumn(binary.left) - elif binary.right.table is join.right: - binary.right = _OuterJoinColumn(binary.right) - clauses.append(visitors.cloned_traverse(join.onclause, {}, {'binary':visit_binary})) - else: - clauses.append(join.onclause) + clauses.append(join.onclause) for j in join.left, join.right: if isinstance(j, Join): visit_join(j) @@ -99,26 +77,23 @@ class SQLBase7Compiler(CompilerBase): return and_(*clauses) return None - def visit_outer_join_column(self, vc): - return self.process(vc.column) + "(+)" - - -class _OuterJoinColumn(ClauseElement): - __visit_name__ = 'outer_join_column' - - def __init__(self, column): - self.column = column - + def visit_ilike_op(self, binary, **kw): + escape = binary.modifiers.get("escape", None) + return '@lower(%s) LIKE @lower(%s)' % ( + self.process(binary.left, **kw), + self.process(binary.right, **kw)) \ + + (escape and ' ESCAPE \'%s\'' % escape or '') + class SQLBase7Dialect(DefaultDialect): - - name = 'sqlbase7' - statement_compiler = SQLBase7Compiler + name = 'sqlbase7' max_identifier_length = 18 - _type_map = { + statement_compiler = SQLBase7Compiler + + type_map = { 'CHAR' : types.CHAR, 'DATE' : types.DATE, 'DECIMAL' : types.DECIMAL, @@ -129,29 +104,59 @@ class SQLBase7Dialect(DefaultDialect): 'VARCHAR' : types.VARCHAR, } - def create_connect_args(self, url): - connection_string = ';'.join(( - "DRIVER={Centura SQLBase 3.5 32-bit Driver -NT & Win95}", - "SERVER=%s" % url.host, - "DATABASE=%s" % url.database, - "UID=%s" % url.username, - "PWD=%s" % url.password, - )) - return [connection_string], {} + def _check_unicode_returns(self, connection): + return False - def get_default_schema_name(self, connection): - return 'SYSADM' - - def do_execute(self, cursor, statement, parameters, context=None): - # For some (perhaps good?) reason, the SQLBase ODBC driver doesn't like - # parameters if they're of Unicode or Long type. I'd hoped at first that - # the "supports_unicode_binds" attribute would take care of the Unicode - # problem but it didn't seem to. And now the Long parameters seem to - # throw the same error, so... - parameters = list(parameters) - for i, parameter in enumerate(parameters): - if isinstance(parameter, unicode): - parameters[i] = str(parameter) - elif isinstance(parameter, long): - parameters[i] = int(parameter) - super(SQLBase7Dialect, self).do_execute(cursor, statement, tuple(parameters), context) + def get_table_names(self, connection, schema=None, **kw): + if schema is None: + schema = '' + else: + schema = '%s.' % schema + + cursor = connection.connection.cursor() + table_names = [row.NAME for row in cursor.execute( + "SELECT NAME FROM %sSYSTABLES WHERE REMARKS IS NOT NULL" % schema + )] + cursor.close() + return table_names + + def get_columns(self, connection, table_name, schema=None, **kw): + if schema is None: + schema = '' + else: + schema = '%s.' % schema + + cursor = connection.connection.cursor() + columns = [] + + for row in cursor.execute("SELECT NAME,COLTYPE,NULLS FROM %sSYSCOLUMNS WHERE TBNAME = '%s'" % (schema, table_name)): + + columns.append({ + 'name' : row.NAME, + 'type' : self.type_map[row.COLTYPE], + 'nullable' : row.NULLS == 'Y', + 'default' : None, + 'autoincrement' : False, + }) + + cursor.close() + return columns + + def get_primary_keys(self, connection, table_name, schema=None, **kw): + if schema is None: + schema = '' + else: + schema = '%s.' % schema + + cursor = connection.connection.cursor() + primary_keys = [row.COLNAME for row in cursor.execute( + "SELECT COLNAME FROM %sSYSPKCONSTRAINTS WHERE NAME = '%s' ORDER BY PKCOLSEQNUM" % (schema, table_name) + )] + cursor.close() + return primary_keys + + def get_foreign_keys(self, connection, table_name, schema=None, **kw): + return [] + + def get_indexes(self, connection, table_name, schema=None, **kw): + return [] diff --git a/sqlbase7_sa/pyodbc.py b/sqlbase7_sa/pyodbc.py new file mode 100644 index 0000000..b56d374 --- /dev/null +++ b/sqlbase7_sa/pyodbc.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +################################################################################ +# +# SQLBase7-SA -- SQLAlchemy driver/dialect for Centura SQLBase v7 +# Copyright © 2010 Lance Edgar +# +# This file is part of SQLBase7-SA. +# +# SQLBase7-SA is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SQLBase7-SA 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SQLBase7-SA. If not, see . +# +################################################################################ + + +from sqlalchemy.connectors.pyodbc import PyODBCConnector + +from sqlbase7_sa.base import SQLBase7Dialect + + +class SQLBase7_pyodbc(PyODBCConnector, SQLBase7Dialect): + + def create_connect_args(self, url): + connection_string = ';'.join(( + "DRIVER={Centura SQLBase 3.5 32-bit Driver -NT & Win95}", + "SERVER=%s" % url.host, + "DATABASE=%s" % url.database, + "UID=%s" % url.username, + "PWD=%s" % url.password, + )) + return [connection_string], {} + + +dialect = SQLBase7_pyodbc diff --git a/sqlbase7_sa/sqlbase7_sa05.py b/sqlbase7_sa/sqlbase7_sa05.py deleted file mode 100644 index 83a5b79..0000000 --- a/sqlbase7_sa/sqlbase7_sa05.py +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -################################################################################ -# -# SQLBase7-SA -- SQLAlchemy driver/dialect for Centura SQLBase v7 -# Copyright © 2010 Lance Edgar -# -# This file is part of SQLBase7-SA. -# -# SQLBase7-SA is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# SQLBase7-SA 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with SQLBase7-SA. If not, see . -# -################################################################################ - - -from sqlalchemy import types, Column, PrimaryKeyConstraint -from sqlalchemy.sql.compiler import OPERATORS -from sqlalchemy.sql import operators as sql_operators - -from sqlbase7_sa.sqlbase7 import SQLBase7Compiler, SQLBase7Dialect - - -class SQLBase7Compiler_SA05(SQLBase7Compiler): - - operators = SQLBase7Compiler.operators.copy() - operators.update({ - sql_operators.ilike_op: lambda x, y, escape=None: "@lower(%s) LIKE @lower(%s)" % (x, y) + (escape and ' ESCAPE \'%s\'' % escape or ''), - }) - - -class SQLBase7Dialect_SA05(SQLBase7Dialect): - - statement_compiler = SQLBase7Compiler_SA05 - - @classmethod - def dbapi(cls): - import pyodbc - return pyodbc - - def table_names(self, connection, schema): - cursor = connection.connection.cursor() - table_names = [row.NAME for row in cursor.execute( - "SELECT NAME FROM %s.SYSTABLES WHERE REMARKS IS NOT NULL" % schema - )] - cursor.close() - return table_names - - def reflecttable(self, connection, table, include_columns=None): - if table.schema is None: - table.schema = connection.default_schema_name() - - sql = "SELECT NAME,COLTYPE,NULLS FROM %s.SYSCOLUMNS WHERE TBNAME = '%s'" % (table.schema, table.name) - if include_columns: - sql += " AND NAME NOT IN (%s)" % ','.join(include_columns) - cursor = connection.connection.cursor() - for row in cursor.execute(sql): - table.append_column(Column(row.NAME, self._type_map[row.COLTYPE])) - cursor.close() - - cursor = connection.connection.cursor() - key_columns = [row.COLNAME for row in cursor.execute( - "SELECT COLNAME FROM %s.SYSPKCONSTRAINTS WHERE NAME = '%s' ORDER BY PKCOLSEQNUM" % (table.schema, table.name) - )] - if key_columns: - table.append_constraint(PrimaryKeyConstraint(*key_columns)) - cursor.close() diff --git a/sqlbase7_sa/sqlbase7_sa06.py b/sqlbase7_sa/sqlbase7_sa06.py deleted file mode 100644 index 0e9ee12..0000000 --- a/sqlbase7_sa/sqlbase7_sa06.py +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -################################################################################ -# -# SQLBase7-SA -- SQLAlchemy driver/dialect for Centura SQLBase v7 -# Copyright © 2010 Lance Edgar -# -# This file is part of SQLBase7-SA. -# -# SQLBase7-SA is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# SQLBase7-SA 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with SQLBase7-SA. If not, see . -# -################################################################################ - - -from sqlbase7_sa.sqlbase7 import SQLBase7Compiler, SQLBase7Dialect - -from sqlalchemy.connectors.pyodbc import PyODBCConnector - - -class SQLBase7Compiler_SA06(SQLBase7Compiler): - - def visit_ilike_op(self, binary, **kw): - escape = binary.modifiers.get("escape", None) - return '@lower(%s) LIKE @lower(%s)' % ( - self.process(binary.left, **kw), - self.process(binary.right, **kw)) \ - + (escape and ' ESCAPE \'%s\'' % escape or '') - - -class SQLBase7Dialect_SA06(SQLBase7Dialect): - - statement_compiler = SQLBase7Compiler_SA06 - - def _get_default_schema_name(self, connection): - return 'SYSADM' - - def _check_unicode_returns(self, connection): - return False - - def get_table_names(self, connection, schema=None, **kw): - cursor = connection.connection.cursor() - table_names = [row.NAME for row in cursor.execute( - "SELECT NAME FROM %s.SYSTABLES WHERE REMARKS IS NOT NULL" % schema - )] - cursor.close() - return table_names - - def get_columns(self, connection, table_name, schema=None, **kw): - if schema is None: - schema = self.get_default_schema_name(connection) - cursor = connection.connection.cursor() - columns = [] - - for row in cursor.execute("SELECT NAME,COLTYPE,NULLS FROM %s.SYSCOLUMNS WHERE TBNAME = '%s'" % (schema, table_name)): - - columns.append({ - 'name' : row.NAME, - 'type' : self._type_map[row.COLTYPE], - 'nullable' : row.NULLS == 'Y', - 'default' : None, - 'autoincrement' : False, - }) - - cursor.close() - return columns - - def get_primary_keys(self, connection, table_name, schema=None, **kw): - if schema is None: - schema = self.get_default_schema_name(connection) - cursor = connection.connection.cursor() - primary_keys = [row.COLNAME for row in cursor.execute( - "SELECT COLNAME FROM %s.SYSPKCONSTRAINTS WHERE NAME = '%s' ORDER BY PKCOLSEQNUM" % (schema, table_name) - )] - cursor.close() - return primary_keys - - def get_foreign_keys(self, connection, table_name, schema=None, **kw): - return [] - - def get_indexes(self, connection, table_name, schema=None, **kw): - return [] - - -class SQLBase7Dialect_SA06_pyodbc(SQLBase7Dialect_SA06, PyODBCConnector): - pass - - -dialect = SQLBase7Dialect_SA06_pyodbc diff --git a/sqlbase7_sa/tests/__init__.py b/sqlbase7_sa/tests/__init__.py index 420a860..be9e204 100644 --- a/sqlbase7_sa/tests/__init__.py +++ b/sqlbase7_sa/tests/__init__.py @@ -51,5 +51,6 @@ class ConnectionTestCase(TestCase): class ReflectionTestCase(TestCase): def runTest(self): - metadata = MetaData(bind=self.engine, reflect=True) + metadata = MetaData(bind=self.engine) + metadata.reflect(schema='SYSADM') self.assert_(metadata.tables)