Skip to content
Open
12 changes: 7 additions & 5 deletions ACKNOWLEDGMENTS
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
THIS PROJECT IS DERIVED FROM THE FOLLOWING PROJECTS/FORKS:
- https://github.com/LocusEnergy/sqlalchemy-vertica-python
THIS PROJECT IS THE MAINLY DERIVED FROM startappdev repo:
- https://github.com/startappdev/sqlalchemy-vertica

THIS PROJECT WAS ALSO DERIVED FROM THE FOLLOWING PROJECTS:

- https://github.com/zzzeek/sqlalchemy
- https://github.com/bluelabsio/vertica-sqlalchemy
- https://github.com/dennisobrien/sqlalchemy-vertica-python
- https://github.com/Eighty20/sqlalchemy-vertica-python

THANKS TO ALL THE GREAT PEOPLE WHO'VE CONTRIBUTED TO THESE PROJECTS.
- https://github.com/LocusEnergy/sqlalchemy-vertica-python
- https://github.com/dennisobrien/sqlalchemy-vertica-python
10 changes: 8 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@ sqlalchemy-vertica

Vertica dialect for sqlalchemy.

Forked from the `Vertica dialect for sqlalchemy using vertica-python <https://pypi.python
.org/pypi/sqlalchemy-vertica-python>`_.
Forked from the `sqlalchemy-vertica repository <https://github.com/startappdev/sqlalchemy-vertica>`.
Unfortunately, sqlalchemy-vertica was removed from pypi. As of Sept 28, 2018 this version supports
querying views. This is version is not a state of the art, nor does it follow the principles
outlinedb by SQLAlchemy at:https://github.com/zzzeek/sqlalchemy/blob/master/README.dialects.rst.
However, I will slowly start upgrading the code base to meet standards and submit so that it
becomes an external dialect in SQLAlchemy. Anyone interested in helping is welcome to contribute
and/or submit issues/ideas.


.. code-block:: python

Expand Down
11 changes: 5 additions & 6 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
with open("README.rst", "r") as f:
description = f.read()

version_info = (0, 2, 5)
version_info = (0, 0, 5)
version = '.'.join(map(str, version_info))

setup(
Expand All @@ -12,10 +12,10 @@
description='Vertica dialect for sqlalchemy',
long_description=description,
license='MIT',
url='https://github.com/startappdev/sqlalchemy-vertica',
download_url='https://github.com/startappdev/sqlalchemy-vertica/tarball/%s' % (version,),
author='StartApp Inc.',
author_email='ben.feinstein@startapp.com',
url='https://github.com/lv10/sqlalchemy-vertica',
download_url='https://github.com/lv10/sqlalchemy-vertica/tarball/%s' % (version,),
author='Luis Villamarin',
author_email='luis@lv10.me',
packages=(
'sqlalchemy_vertica',
),
Expand All @@ -28,7 +28,6 @@
'pyodbc>=4.0.16',
],
'vertica-python': [
'psycopg2>=2.7.1',
'vertica-python>=0.7.3',
],
},
Expand Down
163 changes: 147 additions & 16 deletions sqlalchemy_vertica/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
import re
from sqlalchemy import exc
from sqlalchemy import sql
from sqlalchemy import util
from textwrap import dedent

from sqlalchemy.dialects.postgresql import BYTEA, DOUBLE_PRECISION
from sqlalchemy.dialects.postgresql import BYTEA, DOUBLE_PRECISION, INTERVAL
from sqlalchemy.dialects.postgresql.base import PGDialect, PGDDLCompiler
from sqlalchemy.engine import reflection
from sqlalchemy.types import INTEGER, BIGINT, SMALLINT, VARCHAR, CHAR, \
NUMERIC, FLOAT, REAL, DATE, DATETIME, BOOLEAN, BLOB, TIMESTAMP, TIME
from sqlalchemy.sql import sqltypes

ischema_names = {
'INT': INTEGER,
Expand All @@ -32,8 +34,11 @@
'DOUBLE': DOUBLE_PRECISION,
'TIMESTAMP': TIMESTAMP,
'TIMESTAMP WITH TIMEZONE': TIMESTAMP,
'TIMESTAMPTZ': TIMESTAMP(timezone=True),
'TIME': TIME,
'TIME WITH TIMEZONE': TIME,
'TIMETZ': TIME(timezone=True),
'INTERVAL': INTERVAL,
'DATE': DATE,
'DATETIME': DATETIME,
'SMALLDATETIME': DATETIME,
Expand All @@ -42,6 +47,9 @@
'RAW': BLOB,
'BYTEA': BYTEA,
'BOOLEAN': BOOLEAN,
'LONG VARBINARY': BLOB,
'LONG VARCHAR': VARCHAR,
'GEOMETRY': BLOB,
}


Expand Down Expand Up @@ -151,20 +159,44 @@ def get_schema_names(self, connection, **kw):
c = connection.execute(get_schemas_sql)
return [row[0] for row in c if not row[0].startswith('v_')]

@reflection.cache
def get_table_comment(self, connection, table_name, schema=None, **kw):
if schema is None:
schema_conditional = ""
else:
schema_conditional = "AND object_schema = '{schema}'".format(schema=schema)
query = """
SELECT
comment
FROM
v_catalog.comments
WHERE
object_type = 'TABLE'
AND
object_name = :table_name
{schema_conditional}
""".format(schema_conditional=schema_conditional)
c = connection.execute(sql.text(query), table_name=table_name)
return {"text": c.scalar()}

@reflection.cache
def get_table_oid(self, connection, table_name, schema=None, **kw):
if schema is None:
schema = self._get_default_schema_name(connection)

get_oid_sql = sql.text(dedent("""
SELECT table_id
FROM v_catalog.tables
WHERE lower(table_name) = '%(table)s'
AND lower(table_schema) = '%(schema)s'
SELECT A.table_id
FROM
(SELECT table_id, table_name, table_schema FROM v_catalog.tables
UNION
SELECT table_id, table_name, table_schema FROM v_catalog.views) AS A
WHERE lower(A.table_name) = '%(table)s'
AND lower(A.table_schema) = '%(schema)s'
""" % {'schema': schema.lower(), 'table': table_name.lower()}))

c = connection.execute(get_oid_sql)
table_oid = c.scalar()

if table_oid is None:
raise exc.NoSuchTableError(table_name)
return table_oid
Expand Down Expand Up @@ -256,21 +288,20 @@ def get_columns(self, connection, table_name, schema=None, **kw):
columns = []
for row in connection.execute(s):
name = row.column_name
dtype = row.data_type.upper()
if '(' in dtype:
dtype = dtype.split('(')[0]
coltype = self.ischema_names[dtype]
dtype = row.data_type.lower()
primary_key = name in pk_columns
default = row.column_default
nullable = row.is_nullable

columns.append({
'name': name,
'type': coltype,
'nullable': nullable,
'default': default,
'primary_key': primary_key
})
column_info = self._get_column_info(
name,
dtype,
default,
nullable,
schema,
)
column_info.update({'primary_key': primary_key})
columns.append(column_info)
return columns

@reflection.cache
Expand Down Expand Up @@ -341,3 +372,103 @@ def get_indexes(self, connection, table_name, schema, **kw):
# noinspection PyUnusedLocal
def visit_create_index(self, create):
return None

def _get_column_info(
self,
name,
format_type,
default,
nullable,
schema,
):

# strip (*) from character varying(5), timestamp(5)
# with time zone, geometry(POLYGON), etc.
attype = re.sub(r"\(.*\)", "", format_type)

charlen = re.search(r"\(([\d,]+)\)", format_type)
if charlen:
charlen = charlen.group(1)
args = re.search(r"\((.*)\)", format_type)
if args and args.group(1):
args = tuple(re.split(r"\s*,\s*", args.group(1)))
else:
args = ()
kwargs = {}

if attype == "numeric":
if charlen:
prec, scale = charlen.split(",")
args = (int(prec), int(scale))
else:
args = ()
elif attype == "integer":
args = ()
elif attype in ("timestamptz", "timetz"):
kwargs["timezone"] = True
if charlen:
kwargs["precision"] = int(charlen)
args = ()
elif attype in (
"timestamp",
"time",
):
kwargs["timezone"] = False
if charlen:
kwargs["precision"] = int(charlen)
args = ()
elif attype.startswith("interval"):
field_match = re.match(r"interval (.+)", attype, re.I)
if charlen:
kwargs["precision"] = int(charlen)
if field_match:
kwargs["fields"] = field_match.group(1)
attype = "interval"
args = ()
elif charlen:
args = (int(charlen),)

while True:
if attype.upper() in self.ischema_names:
coltype = self.ischema_names[attype.upper()]
break
else:
coltype = None
break

if coltype:
coltype = coltype(*args, **kwargs)
else:
util.warn(
"Did not recognize type '%s' of column '%s'" % (attype, name)
)
coltype = sqltypes.NULLTYPE
# adjust the default value
autoincrement = False
if default is not None:
match = re.search(r"""(nextval\(')([^']+)('.*$)""", default)
if match is not None:
if issubclass(coltype._type_affinity, sqltypes.Integer):
autoincrement = True
# the default is related to a Sequence
sch = schema
if "." not in match.group(2) and sch is not None:
# unconditionally quote the schema name. this could
# later be enhanced to obey quoting rules /
# "quote schema"
default = (
match.group(1)
+ ('"%s"' % sch)
+ "."
+ match.group(2)
+ match.group(3)
)

column_info = dict(
name=name,
type=coltype,
nullable=nullable,
default=default,
autoincrement=autoincrement,
)
return column_info