Compare commits

..

No commits in common. "master" and "v0.1a3" have entirely different histories.

12 changed files with 192 additions and 369 deletions

View file

@ -1 +0,0 @@
include COPYING.txt

View file

@ -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 <lance@edbob.org>

View file

@ -1,2 +0,0 @@
[egg_info]
tag_build = .dev

View file

@ -33,52 +33,19 @@ execfile(os.path.join(os.path.dirname(__file__), 'sqlbase7_sa', '_version.py'))
setup( setup(
name = 'SQLBase7-SA', name = 'SQLBase7-SA',
version = __version__, version = __version__,
description = 'SQLAlchemy driver/dialect for Centura SQLBase v7',
author = 'Lance Edgar', author = 'Lance Edgar',
author_email = 'lance@edbob.org', 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(), packages = find_packages(),
install_requires = [ install_requires = [
'SQLAlchemy>0.5.2', 'SQLAlchemy>=0.6.0',
], ],
entry_points = { entry_points = {
# SQLAlchemy 0.5
'sqlalchemy.databases' : [
'sqlbase7 = sqlbase7_sa.sqlbase7_sa05:SQLBase7Dialect_SA05',
],
# SQLAlchemy 0.6
'sqlalchemy.dialects' : [ 'sqlalchemy.dialects' : [
'sqlbase7 = sqlbase7_sa.sqlbase7_sa06:SQLBase7Dialect_SA06_pyodbc', 'sqlbase7 = sqlbase7_sa:base.dialect',
], ],
}, },

View file

@ -23,4 +23,11 @@
################################################################################ ################################################################################
from sqlbase7_sa import base
from sqlbase7_sa import pyodbc
from sqlbase7_sa._version import __version__ from sqlbase7_sa._version import __version__
# default dialect
base.dialect = pyodbc.dialect

View file

@ -23,4 +23,4 @@
################################################################################ ################################################################################
__version__ = '0.1b5' __version__ = '0.1a3'

View file

@ -25,16 +25,8 @@
from sqlalchemy.engine.default import DefaultDialect from sqlalchemy.engine.default import DefaultDialect
from sqlalchemy import types, and_ from sqlalchemy import types, and_
from sqlalchemy.sql.compiler import SQLCompiler
from sqlalchemy.sql.expression import Join 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): 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) 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 # 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. # 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) return self.process(join.left, **kwargs) + ", " + self.process(join.right, **kwargs)
def visit_select(self, select, **kwargs): def visit_select(self, select, **kwargs):
if self.stack and 'from' in self.stack[-1]: froms = select._get_display_froms()
existingfroms = self.stack[-1]['from']
else:
existingfroms = None
froms = select._get_display_froms(existingfroms)
whereclause = self._get_join_whereclause(froms) whereclause = self._get_join_whereclause(froms)
if whereclause is not None: if whereclause is not None:
select = select.where(whereclause) select = select.where(whereclause)
@ -71,22 +58,13 @@ class SQLBase7Compiler(CompilerBase):
raise LimitClauseNotSupported(select._limit, select._offset) raise LimitClauseNotSupported(select._limit, select._offset)
kwargs['iswrapper'] = getattr(select, '_is_wrapper', False) 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): def _get_join_whereclause(self, froms):
clauses = [] clauses = []
def visit_join(join): def visit_join(join):
if join.isouter: clauses.append(join.onclause)
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: for j in join.left, join.right:
if isinstance(j, Join): if isinstance(j, Join):
visit_join(j) visit_join(j)
@ -99,26 +77,27 @@ class SQLBase7Compiler(CompilerBase):
return and_(*clauses) return and_(*clauses)
return None return None
def visit_outer_join_column(self, vc): def visit_ilike_op(self, binary, **kw):
return self.process(vc.column) + "(+)" escape = binary.modifiers.get("escape", None)
return '@lower(%s) LIKE @lower(%s)' % (
self.process(binary.left, **kw),
class _OuterJoinColumn(ClauseElement): self.process(binary.right, **kw)) \
__visit_name__ = 'outer_join_column' + (escape and ' ESCAPE \'%s\'' % escape or '')
def __init__(self, column):
self.column = column
class SQLBase7Dialect(DefaultDialect): class SQLBase7Dialect(DefaultDialect):
name = 'sqlbase7' name = 'sqlbase7'
statement_compiler = SQLBase7Compiler
max_identifier_length = 18 max_identifier_length = 18
_type_map = { # # Hmm, it'd be great if these actually did something...
# supports_unicode_statements = False
# supports_unicode_binds = False
statement_compiler = SQLBase7Compiler
type_map = {
'CHAR' : types.CHAR, 'CHAR' : types.CHAR,
'DATE' : types.DATE, 'DATE' : types.DATE,
'DECIMAL' : types.DECIMAL, 'DECIMAL' : types.DECIMAL,
@ -129,29 +108,75 @@ class SQLBase7Dialect(DefaultDialect):
'VARCHAR' : types.VARCHAR, 'VARCHAR' : types.VARCHAR,
} }
def create_connect_args(self, url): def _check_unicode_returns(self, connection):
connection_string = ';'.join(( return False
"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): def get_table_names(self, connection, schema=None, **kw):
return 'SYSADM' 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 do_execute(self, cursor, statement, parameters, context=None): def do_execute(self, cursor, statement, parameters, context=None):
# For some (perhaps good?) reason, the SQLBase ODBC driver doesn't like # 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 # 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 # 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 # problem but it didn't seem to. And now that the Long parameters seem
# throw the same error, so... # to throw the same error, so...
parameters = list(parameters) _parameters = []
for i, parameter in enumerate(parameters): for parameter in parameters:
if isinstance(parameter, unicode): if isinstance(parameter, unicode):
parameters[i] = str(parameter) parameter = str(parameter)
elif isinstance(parameter, long): elif isinstance(parameter, long):
parameters[i] = int(parameter) parameter = int(parameter)
super(SQLBase7Dialect, self).do_execute(cursor, statement, tuple(parameters), context) _parameters.append(parameter)
parameters = tuple(_parameters)
cursor.execute(statement, parameters)

44
sqlbase7_sa/pyodbc.py Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
#
################################################################################
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

View file

@ -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 <http://www.gnu.org/licenses/>.
#
################################################################################
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()

View file

@ -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 <http://www.gnu.org/licenses/>.
#
################################################################################
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

View file

@ -51,5 +51,6 @@ class ConnectionTestCase(TestCase):
class ReflectionTestCase(TestCase): class ReflectionTestCase(TestCase):
def runTest(self): def runTest(self):
metadata = MetaData(bind=self.engine, reflect=True) metadata = MetaData(bind=self.engine)
metadata.reflect(schema='SYSADM')
self.assert_(metadata.tables) self.assert_(metadata.tables)