From ad845f576589e2dc1a822c2c9c6ae4ca30ef1c3b Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 27 Apr 2010 16:26:39 -0500 Subject: [PATCH 02/31] Added unicode parameter workaround. --- sqlbase7_sa/base.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/sqlbase7_sa/base.py b/sqlbase7_sa/base.py index be7dfd1..cc8644f 100644 --- a/sqlbase7_sa/base.py +++ b/sqlbase7_sa/base.py @@ -91,6 +91,10 @@ class SQLBase7Dialect(DefaultDialect): max_identifier_length = 18 + # # Hmm, it'd be great if these actually did something... + # supports_unicode_statements = False + # supports_unicode_binds = False + statement_compiler = SQLBase7Compiler type_map = { @@ -160,3 +164,14 @@ class SQLBase7Dialect(DefaultDialect): def get_indexes(self, connection, table_name, schema=None, **kw): return [] + + def do_execute(self, cursor, statement, parameters, context=None): + # Since the "supports_unicode_binds" attribute doesn't seem to do + # anything, take matters into our own hands here. + _parameters = [] + for parameter in parameters: + if isinstance(parameter, basestring) and not isinstance(parameter, str): + parameter = str(parameter) + _parameters.append(parameter) + parameters = tuple(_parameters) + cursor.execute(statement, parameters) From 64ae45af90e1eecf5d248863d19885cd8e6f3988 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 27 Apr 2010 16:27:20 -0500 Subject: [PATCH 03/31] Bumped version to 0.1a2. --- sqlbase7_sa/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlbase7_sa/_version.py b/sqlbase7_sa/_version.py index 5f74cfc..fc24ab7 100644 --- a/sqlbase7_sa/_version.py +++ b/sqlbase7_sa/_version.py @@ -23,4 +23,4 @@ ################################################################################ -__version__ = '0.1a1' +__version__ = '0.1a2' From 6423627665d7eb057a2336f816df9bec200e8fc3 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 4 May 2010 15:31:15 -0500 Subject: [PATCH 05/31] Added "long" type bind parameter check. --- sqlbase7_sa/base.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/sqlbase7_sa/base.py b/sqlbase7_sa/base.py index cc8644f..c5aaba2 100644 --- a/sqlbase7_sa/base.py +++ b/sqlbase7_sa/base.py @@ -166,12 +166,17 @@ class SQLBase7Dialect(DefaultDialect): return [] def do_execute(self, cursor, statement, parameters, context=None): - # Since the "supports_unicode_binds" attribute doesn't seem to do - # anything, take matters into our own hands here. + # 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 that the Long parameters seem + # to throw the same error, so... _parameters = [] for parameter in parameters: - if isinstance(parameter, basestring) and not isinstance(parameter, str): + if isinstance(parameter, unicode): parameter = str(parameter) + elif isinstance(parameter, long): + parameter = int(parameter) _parameters.append(parameter) parameters = tuple(_parameters) cursor.execute(statement, parameters) From 34e32041f5ec81ce4d8b4d6da5d63e56b11d7984 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 4 May 2010 15:31:43 -0500 Subject: [PATCH 06/31] Bumped version to 0.1a3. --- sqlbase7_sa/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlbase7_sa/_version.py b/sqlbase7_sa/_version.py index fc24ab7..4732ef7 100644 --- a/sqlbase7_sa/_version.py +++ b/sqlbase7_sa/_version.py @@ -23,4 +23,4 @@ ################################################################################ -__version__ = '0.1a2' +__version__ = '0.1a3' From 617c28e8c786b7ccb7f54329b3073d237e1b7726 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 21 May 2010 18:29:49 -0500 Subject: [PATCH 08/31] Added support for SA 0.5 (previously only 0.6 was supported). --- setup.py | 3 +- sqlbase7_sa/__init__.py | 25 ++++-- sqlbase7_sa/pyodbc.py | 44 ---------- sqlbase7_sa/{base.py => sqlbase7.py} | 116 ++++++++------------------- sqlbase7_sa/sqlbase7_sa05.py | 66 +++++++++++++++ sqlbase7_sa/sqlbase7_sa06.py | 99 +++++++++++++++++++++++ sqlbase7_sa/tests/__init__.py | 3 +- 7 files changed, 221 insertions(+), 135 deletions(-) delete mode 100644 sqlbase7_sa/pyodbc.py rename sqlbase7_sa/{base.py => sqlbase7.py} (52%) create mode 100644 sqlbase7_sa/sqlbase7_sa05.py create mode 100644 sqlbase7_sa/sqlbase7_sa06.py diff --git a/setup.py b/setup.py index c2bd74d..1ead048 100644 --- a/setup.py +++ b/setup.py @@ -40,9 +40,10 @@ setup( packages = find_packages(), install_requires = [ - 'SQLAlchemy>=0.6.0', + 'SQLAlchemy>=0.5,<=0.6.99', ], + # This is only used by SQLAlchemy 0.6. entry_points = { 'sqlalchemy.dialects' : [ 'sqlbase7 = sqlbase7_sa:base.dialect', diff --git a/sqlbase7_sa/__init__.py b/sqlbase7_sa/__init__.py index 96991e6..1c23c57 100644 --- a/sqlbase7_sa/__init__.py +++ b/sqlbase7_sa/__init__.py @@ -23,11 +23,26 @@ ################################################################################ -from sqlbase7_sa import base -from sqlbase7_sa import pyodbc - from sqlbase7_sa._version import __version__ +import sqlalchemy +from pkg_resources import parse_version -# default dialect -base.dialect = pyodbc.dialect + +if parse_version(sqlalchemy.__version__) <= parse_version('0.5.99'): + + # SQLAlchemy 0.5 doesn't support user-contributed dialects "directly" + # by way of setuptools entry points, so we must monkey-patch it in + # order to add ours. + import sqlalchemy.databases, sqlbase7_sa, sys + sqlalchemy.databases.sqlbase7 = sqlbase7_sa + sys.modules['sqlalchemy.databases.sqlbase7'] = sqlbase7_sa + + # SQLAlchemy will be expecting us to have a 'dialect' attribute. + import sqlbase7_sa.sqlbase7_sa05 + dialect = sqlbase7_sa.sqlbase7_sa05.SQLBase7Dialect_SA05 + +else: + # SQLAlchemy 0.6 is much nicer to play with. + import sqlbase7_sa.sqlbase7_sa06 + base = sqlbase7_sa.sqlbase7_sa06 diff --git a/sqlbase7_sa/pyodbc.py b/sqlbase7_sa/pyodbc.py deleted file mode 100644 index b56d374..0000000 --- a/sqlbase7_sa/pyodbc.py +++ /dev/null @@ -1,44 +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.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/base.py b/sqlbase7_sa/sqlbase7.py similarity index 52% rename from sqlbase7_sa/base.py rename to sqlbase7_sa/sqlbase7.py index c5aaba2..93c8ef3 100644 --- a/sqlbase7_sa/base.py +++ b/sqlbase7_sa/sqlbase7.py @@ -25,10 +25,17 @@ from sqlalchemy.engine.default import DefaultDialect from sqlalchemy import types, and_ -from sqlalchemy.sql.compiler import SQLCompiler from sqlalchemy.sql.expression import Join +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): def __init__(self, limit, offset): @@ -39,7 +46,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(SQLCompiler): +class SQLBase7Compiler(CompilerBase): # 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. @@ -58,7 +65,7 @@ class SQLBase7Compiler(SQLCompiler): raise LimitClauseNotSupported(select._limit, select._offset) kwargs['iswrapper'] = getattr(select, '_is_wrapper', False) - return SQLCompiler.visit_select(self, select, **kwargs) + return super(SQLBase7Compiler, self).visit_select(select, **kwargs) def _get_join_whereclause(self, froms): clauses = [] @@ -77,27 +84,16 @@ class SQLBase7Compiler(SQLCompiler): return and_(*clauses) return None - 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' - max_identifier_length = 18 - - # # Hmm, it'd be great if these actually did something... - # supports_unicode_statements = False - # supports_unicode_binds = False - statement_compiler = SQLBase7Compiler - type_map = { + max_identifier_length = 18 + + _type_map = { 'CHAR' : types.CHAR, 'DATE' : types.DATE, 'DECIMAL' : types.DECIMAL, @@ -108,75 +104,29 @@ class SQLBase7Dialect(DefaultDialect): 'VARCHAR' : types.VARCHAR, } - def _check_unicode_returns(self, connection): - return False - - 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 [] + 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 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 that the Long parameters seem - # to throw the same error, so... - _parameters = [] - for parameter in parameters: + # 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): - parameter = str(parameter) + parameters[i] = str(parameter) elif isinstance(parameter, long): - parameter = int(parameter) - _parameters.append(parameter) - parameters = tuple(_parameters) - cursor.execute(statement, parameters) + parameters[i] = int(parameter) + super(SQLBase7Dialect, self).do_execute(cursor, statement, tuple(parameters), context) diff --git a/sqlbase7_sa/sqlbase7_sa05.py b/sqlbase7_sa/sqlbase7_sa05.py new file mode 100644 index 0000000..642ada1 --- /dev/null +++ b/sqlbase7_sa/sqlbase7_sa05.py @@ -0,0 +1,66 @@ +#!/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 + +from sqlbase7_sa.sqlbase7 import SQLBase7Dialect + + +OPERATORS[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): + + @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() + for row in cursor.execute("SELECT COLNAME FROM %s.SYSPKCONSTRAINTS WHERE NAME = '%s' ORDER BY PKCOLSEQNUM" % (table.schema, table.name)): + table.append_constraint(PrimaryKeyConstraint(row.COLNAME)) + cursor.close() diff --git a/sqlbase7_sa/sqlbase7_sa06.py b/sqlbase7_sa/sqlbase7_sa06.py new file mode 100644 index 0000000..0e9ee12 --- /dev/null +++ b/sqlbase7_sa/sqlbase7_sa06.py @@ -0,0 +1,99 @@ +#!/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 be9e204..420a860 100644 --- a/sqlbase7_sa/tests/__init__.py +++ b/sqlbase7_sa/tests/__init__.py @@ -51,6 +51,5 @@ class ConnectionTestCase(TestCase): class ReflectionTestCase(TestCase): def runTest(self): - metadata = MetaData(bind=self.engine) - metadata.reflect(schema='SYSADM') + metadata = MetaData(bind=self.engine, reflect=True) self.assert_(metadata.tables) From c2ba733f0ddfa495cbab2c0d1c4ca5484f701fe7 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 21 May 2010 19:50:23 -0500 Subject: [PATCH 09/31] Fixed primary key reflection for SA 0.5. --- sqlbase7_sa/sqlbase7_sa05.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sqlbase7_sa/sqlbase7_sa05.py b/sqlbase7_sa/sqlbase7_sa05.py index 642ada1..2b98c83 100644 --- a/sqlbase7_sa/sqlbase7_sa05.py +++ b/sqlbase7_sa/sqlbase7_sa05.py @@ -61,6 +61,9 @@ class SQLBase7Dialect_SA05(SQLBase7Dialect): cursor.close() cursor = connection.connection.cursor() - for row in cursor.execute("SELECT COLNAME FROM %s.SYSPKCONSTRAINTS WHERE NAME = '%s' ORDER BY PKCOLSEQNUM" % (table.schema, table.name)): - table.append_constraint(PrimaryKeyConstraint(row.COLNAME)) + 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() From 782c60b263497bbdbbff734d14b2d4ee58f54fff Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 21 May 2010 20:08:32 -0500 Subject: [PATCH 10/31] Added .dev build tag; bumped version to 0.1a4. --- setup.cfg | 2 ++ sqlbase7_sa/_version.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 setup.cfg diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..9c7b914 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[egg_info] +tag_build = .dev diff --git a/sqlbase7_sa/_version.py b/sqlbase7_sa/_version.py index 4732ef7..181bbdb 100644 --- a/sqlbase7_sa/_version.py +++ b/sqlbase7_sa/_version.py @@ -23,4 +23,4 @@ ################################################################################ -__version__ = '0.1a3' +__version__ = '0.1a4' From 416a710dff3c516ca0a42a02e0a3748b04d52304 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 21 May 2010 20:14:15 -0500 Subject: [PATCH 13/31] Bumped version to 0.1a5. --- sqlbase7_sa/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlbase7_sa/_version.py b/sqlbase7_sa/_version.py index 181bbdb..e49c503 100644 --- a/sqlbase7_sa/_version.py +++ b/sqlbase7_sa/_version.py @@ -23,4 +23,4 @@ ################################################################################ -__version__ = '0.1a4' +__version__ = '0.1a5' From 4369f88c548c1fe00ff570584897c312ec1636f7 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 27 May 2010 13:00:39 -0500 Subject: [PATCH 14/31] Fixed operator overloading in SA05 dialect; was doing so globally instead of dialect-local. --- sqlbase7_sa/sqlbase7_sa05.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/sqlbase7_sa/sqlbase7_sa05.py b/sqlbase7_sa/sqlbase7_sa05.py index 2b98c83..83a5b79 100644 --- a/sqlbase7_sa/sqlbase7_sa05.py +++ b/sqlbase7_sa/sqlbase7_sa05.py @@ -25,16 +25,23 @@ from sqlalchemy import types, Column, PrimaryKeyConstraint from sqlalchemy.sql.compiler import OPERATORS -from sqlalchemy.sql import operators +from sqlalchemy.sql import operators as sql_operators -from sqlbase7_sa.sqlbase7 import SQLBase7Dialect +from sqlbase7_sa.sqlbase7 import SQLBase7Compiler, SQLBase7Dialect -OPERATORS[operators.ilike_op] = lambda x, y, escape=None: "@lower(%s) LIKE @lower(%s)" % (x, y) + (escape and ' ESCAPE \'%s\'' % escape or '') +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 From 2b9957d2a103758dc5f291d2761dd2c3bf18e24f Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 28 May 2010 10:05:02 -0500 Subject: [PATCH 15/31] Added entry point for SA 0.5 (whoops); emptied __init__ module. --- setup.py | 12 +++++++++--- sqlbase7_sa/__init__.py | 22 ---------------------- 2 files changed, 9 insertions(+), 25 deletions(-) diff --git a/setup.py b/setup.py index 1ead048..bb12dd6 100644 --- a/setup.py +++ b/setup.py @@ -40,13 +40,19 @@ setup( packages = find_packages(), install_requires = [ - 'SQLAlchemy>=0.5,<=0.6.99', + 'SQLAlchemy', ], - # This is only used by SQLAlchemy 0.6. entry_points = { + + # SQLAlchemy 0.5 + 'sqlalchemy.databases' : [ + 'sqlbase7 = sqlbase7_sa.sqlbase7_sa05:SQLBase7Dialect_SA05', + ], + + # SQLAlchemy 0.6 'sqlalchemy.dialects' : [ - 'sqlbase7 = sqlbase7_sa:base.dialect', + 'sqlbase7 = sqlbase7_sa.sqlbase7_sa06:SQLBase7Dialect_SA06_pyodbc', ], }, diff --git a/sqlbase7_sa/__init__.py b/sqlbase7_sa/__init__.py index 1c23c57..a7c7be0 100644 --- a/sqlbase7_sa/__init__.py +++ b/sqlbase7_sa/__init__.py @@ -24,25 +24,3 @@ from sqlbase7_sa._version import __version__ - -import sqlalchemy -from pkg_resources import parse_version - - -if parse_version(sqlalchemy.__version__) <= parse_version('0.5.99'): - - # SQLAlchemy 0.5 doesn't support user-contributed dialects "directly" - # by way of setuptools entry points, so we must monkey-patch it in - # order to add ours. - import sqlalchemy.databases, sqlbase7_sa, sys - sqlalchemy.databases.sqlbase7 = sqlbase7_sa - sys.modules['sqlalchemy.databases.sqlbase7'] = sqlbase7_sa - - # SQLAlchemy will be expecting us to have a 'dialect' attribute. - import sqlbase7_sa.sqlbase7_sa05 - dialect = sqlbase7_sa.sqlbase7_sa05.SQLBase7Dialect_SA05 - -else: - # SQLAlchemy 0.6 is much nicer to play with. - import sqlbase7_sa.sqlbase7_sa06 - base = sqlbase7_sa.sqlbase7_sa06 From 5021d47ff4958f7bd5d3602de15dbe7192faa490 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 28 May 2010 10:16:48 -0500 Subject: [PATCH 16/31] Bumped version to 0.1b1. --- sqlbase7_sa/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlbase7_sa/_version.py b/sqlbase7_sa/_version.py index e49c503..729541a 100644 --- a/sqlbase7_sa/_version.py +++ b/sqlbase7_sa/_version.py @@ -23,4 +23,4 @@ ################################################################################ -__version__ = '0.1a5' +__version__ = '0.1b1' From e68268c283bc613be01b35957f50fc30fb311432 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 28 May 2010 10:17:15 -0500 Subject: [PATCH 17/31] Fleshed out setup.py in prep for PyPI upload. --- setup.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index bb12dd6..eb6ec34 100644 --- a/setup.py +++ b/setup.py @@ -33,9 +33,35 @@ 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', + url = "http://sqlbase7-sa.edbob.org/", + 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(), From 8fb11fd269f45ce9a2f42b2167f22be838763301 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 28 May 2010 10:22:20 -0500 Subject: [PATCH 18/31] more upload prep Added manifest.in, gave license file a .txt extension. --- COPYING => COPYING.txt | 0 MANIFEST.in | 1 + 2 files changed, 1 insertion(+) rename COPYING => COPYING.txt (100%) create mode 100644 MANIFEST.in diff --git a/COPYING b/COPYING.txt similarity index 100% rename from COPYING rename to COPYING.txt diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..c8a5f6c --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include COPYING.txt From 2edda5c7b4e4a4292c212e4698a00bb525ce4a01 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 28 May 2010 10:34:59 -0500 Subject: [PATCH 20/31] Bumped version to 0.1b2. --- sqlbase7_sa/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlbase7_sa/_version.py b/sqlbase7_sa/_version.py index 729541a..419df1a 100644 --- a/sqlbase7_sa/_version.py +++ b/sqlbase7_sa/_version.py @@ -23,4 +23,4 @@ ################################################################################ -__version__ = '0.1b1' +__version__ = '0.1b2' From 2fd3f190af5e374f6ebf920d6dbee4e3147fae1b Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 28 May 2010 11:29:49 -0500 Subject: [PATCH 22/31] Bumped version to 0.1b3. --- sqlbase7_sa/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlbase7_sa/_version.py b/sqlbase7_sa/_version.py index 419df1a..daf0c55 100644 --- a/sqlbase7_sa/_version.py +++ b/sqlbase7_sa/_version.py @@ -23,4 +23,4 @@ ################################################################################ -__version__ = '0.1b2' +__version__ = '0.1b3' From f7c4a2c2df0950097d08702d1d16d4ad7494ab01 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 25 Jun 2010 12:58:34 -0500 Subject: [PATCH 23/31] bumped sqlalchemy requirement It's been discovered that SQLAlchemy 0.5.2 won't work; some problem with joining the sales log tables. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index eb6ec34..c3b5602 100644 --- a/setup.py +++ b/setup.py @@ -66,7 +66,7 @@ specific version of this database, that version being 7.5.1. packages = find_packages(), install_requires = [ - 'SQLAlchemy', + 'SQLAlchemy>0.5.2', ], entry_points = { From 8846e9b3725d080345f775c2a63682ba3efa6cdf Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 25 Jun 2010 13:01:15 -0500 Subject: [PATCH 25/31] bumped version --- sqlbase7_sa/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlbase7_sa/_version.py b/sqlbase7_sa/_version.py index daf0c55..9edf306 100644 --- a/sqlbase7_sa/_version.py +++ b/sqlbase7_sa/_version.py @@ -23,4 +23,4 @@ ################################################################################ -__version__ = '0.1b3' +__version__ = '0.1b4' From 3ee47ed87f2289ea39b5ad68f0c9a90a0b0dda5e Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 31 Jul 2010 20:09:17 -0500 Subject: [PATCH 26/31] added outer join support...michael was right :) And once again, thanks to Michael Bayer for forging the way on this one. The Oracle dialect code was still pretty much copied directly over. --- sqlbase7_sa/sqlbase7.py | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/sqlbase7_sa/sqlbase7.py b/sqlbase7_sa/sqlbase7.py index 93c8ef3..690395c 100644 --- a/sqlbase7_sa/sqlbase7.py +++ b/sqlbase7_sa/sqlbase7.py @@ -26,6 +26,7 @@ from sqlalchemy.engine.default import DefaultDialect from sqlalchemy import types, and_ from sqlalchemy.sql.expression import Join +from sqlalchemy.sql import visitors, operators, ClauseElement import sqlalchemy @@ -56,7 +57,12 @@ class SQLBase7Compiler(CompilerBase): return self.process(join.left, **kwargs) + ", " + self.process(join.right, **kwargs) def visit_select(self, select, **kwargs): - froms = select._get_display_froms() + if self.stack and 'from' in self.stack[-1]: + existingfroms = self.stack[-1]['from'] + else: + existingfroms = None + + froms = select._get_display_froms(existingfroms) whereclause = self._get_join_whereclause(froms) if whereclause is not None: select = select.where(whereclause) @@ -71,7 +77,16 @@ class SQLBase7Compiler(CompilerBase): clauses = [] def visit_join(join): - clauses.append(join.onclause) + 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) for j in join.left, join.right: if isinstance(j, Join): visit_join(j) @@ -84,6 +99,16 @@ 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 + class SQLBase7Dialect(DefaultDialect): From 756f0a8e5d54fb2d2a8c15ba85ec7effe31a0a10 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 31 Jul 2010 20:15:49 -0500 Subject: [PATCH 28/31] bumped version --- sqlbase7_sa/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlbase7_sa/_version.py b/sqlbase7_sa/_version.py index 9edf306..5bb4149 100644 --- a/sqlbase7_sa/_version.py +++ b/sqlbase7_sa/_version.py @@ -23,4 +23,4 @@ ################################################################################ -__version__ = '0.1b4' +__version__ = '0.1b5' From bd74701c42b4b83910fefd1c4489c33d5015ef49 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 6 Oct 2025 14:05:17 -0500 Subject: [PATCH 29/31] add readme file, to replace old home page which was at https://sqlalchemy-sa.edbob.org --- README.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..c64eef9 --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ + +# 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 From dafac32f9432fb16b7a9f477012828d7df836ce4 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 6 Oct 2025 14:10:35 -0500 Subject: [PATCH 30/31] update home page URL for project --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c3b5602..2129140 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ setup( version = __version__, author = 'Lance Edgar', author_email = 'lance@edbob.org', - url = "http://sqlbase7-sa.edbob.org/", + url = "https://forgejo.wuttaproject.org/rattail/sqlbase7-sa", license = "GNU GPL v3", description = 'SQLAlchemy dialect for Centura SQLBase v7', long_description = """ From b4c4d1bb26fe424d575fca0c6c834547f18c5e66 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 6 Oct 2025 14:11:42 -0500 Subject: [PATCH 31/31] use unix line endings for setup.py --- setup.py | 172 +++++++++++++++++++++++++++---------------------------- 1 file changed, 86 insertions(+), 86 deletions(-) diff --git a/setup.py b/setup.py index 2129140..75395a7 100644 --- a/setup.py +++ b/setup.py @@ -1,86 +1,86 @@ -#!/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__, + 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', + )