Code Monkey home page Code Monkey logo

sqlalchemy-hana's Introduction

SQLAlchemy dialect for SAP HANA

image

image

This dialect allows you to use the SAP HANA database with SQLAlchemy. It uses hdbcli to connect to SAP HANA. Please notice that sqlalchemy-hana isn't an official SAP product and isn't covered by SAP support.

Prerequisites

  • Python 3.8+
  • SQLAlchemy 1.4 or 2.x
  • hdbcli

Install

Install from the Python Package Index:

$ pip install sqlalchemy-hana

Versioning

sqlalchemy-hana follows the semantic versioning standard, meaning that breaking changes will only be added in major releases. Please note, that only the following modules are considered to be part of the public API

  • sqlalchemy_hana.types
  • sqlalchemy_hana.errors

For these, only exported members (part of __all__ ) are guaranteed to be stable.

Supported HANA Versions/Editions

  • SAP HANA Cloud
  • SAP HANA
  • SAP HANA, express edition

Getting started

If you do not have access to a SAP HANA server, you can also use the SAP HANA Express edition.

After installation of sqlalchemy-hana, you can create a engine which connects to a SAP HANA instance. This engine works like all other engines of SQLAlchemy.

from sqlalchemy import create_engine
engine = create_engine('hana://username:[email protected]:30015')

Alternatively, you can use HDB User Store to avoid entering connection-related information manually each time you want to establish a connection to an SAP HANA database:

from sqlalchemy import create_engine
engine = create_engine('hana://userkey=my_user_store_key')

You can create your user key in the user store using the following command:

hdbuserstore SET <KEY> <host:port> <USERNAME> <PASSWORD>

In case of a tenant database, you may use:

from sqlalchemy import create_engine
engine = engine = create_engine('hana://user:pass@host/tenant_db_name')

Usage

Special CREATE TABLE argument

Sqlalchemy-hana provides a special argument called “hana_table_type” which can be used to specify the type of table one wants to create with SAP HANA (i.e. ROW/COLUMN). The default table type depends on your SAP HANA configuration and version.

t = Table('my_table', metadata, Column('id', Integer), hana_table_type = 'COLUMN')

Case Sensitivity

In SAP HANA, all case insensitive identifiers are represented using uppercase text. In SQLAlchemy on the other hand all lower case identifier names are considered to be case insensitive. The sqlalchemy-hana dialect converts all case insensitive and case sensitive identifiers to the right casing during schema level communication. In the sqlalchemy-hana dialect, using an uppercase name on the SQLAlchemy side indicates a case sensitive identifier, and SQLAlchemy will quote the name,which may cause case mismatches between data received from SAP HANA. Unless identifier names have been truly created as case sensitive (i.e. using quoted names), all lowercase names should be used on the SQLAlchemy side.

LIMIT/OFFSET Support

SAP HANA supports both LIMIT and OFFSET, but it only supports OFFSET in conjunction with LIMIT i.e. in the select statement the offset parameter cannot be set without the LIMIT clause, hence in sqlalchemy-hana if the user tries to use offset without limit, a limit of 2147384648 would be set, this has been done so that the users can smoothly use LIMIT or OFFSET as in other databases that do not have this limitation. 2147384648 was chosen, because it is the maximum number of records per result set.

RETURNING Support

Sqlalchemy-hana does not support RETURNING in the INSERT, UPDATE and DELETE statements to retrieve result sets of matched rows from INSERT, UPDATE and DELETE statements because newly generated primary key values are neither fetched nor returned automatically in SAP HANA and SAP HANA does not support the syntax INSERT... RETURNING....

Reflection

The sqlalchemy-hana dialect supports all reflection capabilities of SQLAlchemy. The Inspector used for the SAP HANA database is an instance of HANAInspector and offers an additional method which returns the OID (object id) for the given table name.

from sqlalchemy import create_engine, inspect

engine = create_engine("hana://username:[email protected]:30015")
insp = inspect(engine)  # will be a HANAInspector
print(insp.get_table_oid('my_table'))

Foreign Key Constraints

In SAP HANA the following UPDATE and DELETE foreign key referential actions are available:

  • RESTRICT
  • CASCADE
  • SET NULL
  • SET DEFAULT

The foreign key referential option NO ACTION does not exist in SAP HANA. The default is RESTRICT.

UNIQUE Constraints

For each unique constraint an index is created in SAP HANA, this may lead to unexpected behavior in programs using reflection.

Data types

As with all SQLAlchemy dialects, all UPPERCASE types that are known to be valid with SAP HANA are importable from the top level dialect, whether they originate from sqlalchemy types or from the local dialect. Therefore all supported types are part of the sqlalchemy_hana.types module and can be used from there.

sqlalchemy-hana aims to support as many SQLAlchemy types as possible and to fallback to a similar type of the requested type is not supported in SAP HANA. The following table shows the mapping:

SQLAlchemy type HANA type
DATETIME TIMESTAMP
NUMERIC DECIMAL
String NVARCHAR
Unicode NVARCHAR
TEXT NCLOB
BINARY VARBINARY
DOUBLE_PRECISION DOUBLE
Uuid NVARCHAR(32)
LargeBinary BLOB
UnicodeText NCLOB

Please note, that some types might not support a length, precision or scale, even if the SQLAlchemy type class accepts them. The type compiler will then just ignore these arguments are render a type which will not lead to a SQL error.

The ARRAY datatype is not supported because hdbcli does not yet provide support for it.

Regex

sqlalchemy-hana supports the regexp_match and regexp_replace functions provided by SQLAlchemy.

Bound Parameter Styles

The default parameter style for the sqlalchemy-hana dialect is qmark, where SQL is rendered using the following style:

WHERE my_column = ?

Boolean

By default, sqlalchemy-hana uses native boolean types. However, older versions of sqlalchemy-hana used integer columns to represent these values leading to a compatibility gap. To disable native boolean support, add use_native_boolean=False to create_engine.

Users are encouraged to switch to native booleans. This can be e.g. done by using alembic:

from sqlalchemy import false

# assuming a table TAB with a tinyint column named valid
def upgrade() -> None:
    op.add_column(Column("TAB", Column('valid_tmp', Boolean, server_default=false())))
    op.get_bind().execute("UPDATE TAB SET valid_tmp = TRUE WHERE valid = 1")
    op.drop_column("TAB", "valid")
    op.get_bind().execute("RENAME COLUMN TAB.valid_tmp to valid")
    # optionally, remove also the server default by using alter column

Computed columns

SAP HANA supports two computed/calculated columns:

  • <col> AS <expr>: the column is fully virtual and the expression is evaluated with each SELECT
  • <col> GENERATED ALWAYS AS <expr>: the expression is evaluated during insertion and the value

    is stored in the table

By default, sqlalchemy-hana creates a GENERATED ALWAYS AS if a Computed column is used. If Computed(persisted=False) is used, a fully virtual column using AS is created.

Views

sqlalchemy-hana supports the creation and usage of SQL views.

The views are not bound to the metadata object, therefore each needs to be created/dropped manually using CreateView and DropView. By using the helper function view, a TableClause object is generated which can be used in select statements. The returned object has the same primary keys as the underlying selectable.

Views can also be used in ORM and e.g. assigned to the __table__ attribute of declarative base classes.

For general information about views, please refer to this page.

from sqlalchemy import Column, Integer, MetaData, String, Table, select
from sqlalchemy_hana.elements import CreateView, DropView, view

engine = None  # a engine bound to a SAP HANA instance
metadata = MetaData()
stuff = sa.Table(
    "stuff",
    metadata,
    Column("id", Integer, primary_key=True),
    Column("data", String(50)),
)

selectable = select(stuff.c.id, stuff.c.data).where(stuff.c.data == "something")

with engine.begin() as conn:
    # create a view
    ddl = CreateView("stuff_view", selectable)
    conn.execute(ddl)

    # usage of a view
    stuff_view = view("stuff_view", selectable)
    select(stuff_view.c.id, stuff_view.c.data).all()

    # drop a view
    ddl = DropView("stuff_view")
    conn.execute(ddl)

Upsert

UPSERT statements are supported with some limitations by sqlalchemy-hana. Caching is disabled due to implementation details and will not be added until a unified insert/upsert/merge implementation is available in SQLAlchemy (see sqlalchemy/sqlalchemy#8321).

from sqlalchemy import Column, Integer, MetaData, String, Table
from sqlalchemy_hana.elements import upsert

engine = None  # a engine bound to a SAP HANA instance
metadata = MetaData()
stuff = sa.Table(
    "stuff",
    metadata,
    Column("id", Integer, primary_key=True),
    Column("data", String(50)),
)

with engine.begin() as conn:
    statement upsert(stuff).values(id=1, data="some").filter_by(id=1)
    conn.execute(statement)

Identity

Identity columns are fully supported but not reflection of those. Therefore, alembic support for identity columns is reduced to creation of those.

Auto-increment

SAP HANA only supports auto-increment with identity columns, therefore an identity will be rendered if needed. This means that the the following constructs are equivalent:

  • Column('some', Integer, autoincrement=True)
  • Column('some', Integer, Identity, autoincrement=True)
  • Column('some', Integer, Identity, autoincrement=True)

Note, that for autoincrement=True a post-execute statement execution is needed to fetch the inserted identity value which might affect performance.

As an SQLAlchemy specific alternative, a sqlalchemy.schema.Sequence can be used to simulate an auto-increment behavior, as followed:

t = Table('my_table', metadata, Column('id', Integer, Sequence('id_seq'), primary key=True))

Note, that on SAP HANA side, the column and the sequence are not linked, meaning that the sequence can be e.g. be incremented w/o an actual insert into the table.

Alembic

The sqlalchemy-hana dialect also contains a dialect for alembic. This dialect is active as soon as alembic is installed. To ensure version compatibility, install sqlalchemy-hana as followed:

$ pip install sqlalchemy-hana[alembic]

Error handling for humans

sqlalchemy-hana provides the sqlalchemy_hana.errors module which contains a set of special exceptions and wrapper methods. SQLAlchemy and hdbcli only provide generic exceptions which are sometimes not very helpful and manual effort is needed to extract the relevant information. To make this easier, the module provides two wrapper functions which take a SQLAlchemy or hdbcli error and raise a more specific exception if possible.

from sqlalchemy_hana.errors import wrap_dbapi_error
from sqlalchemy.exc import DBAPIError

try:
    # some sqlalchemy code which might raise a DBAPIError
except DBAPIError as err:
    wrap_dbapi_error(err)
    # if you reach this line, either the wrapped error of DBAPIError was not a hdbcli error
    # of no more specific exception was found

Development Setup

We recommend the usage of pyenv to install a proper 3.11 python version for development.

  • pyenv install 3.11
  • python311 -m venv venv
  • source venv/bin/activate
  • pip install -U pip
  • pip install -e .[dev,test,alembic]

To execute the tests, use pyenv. The linters and formatters can be executed using pre-commit: pre-commit run -a.

Testing

Pre-Submit: Linters, formatters and test matrix Post-Submit: Linters and formatters

Release Actions

  • Update the version in the pyproject.toml
  • Add an entry in the changelog
  • Push a new tag like vX.X.X to trigger the release

Support, Feedback, Contributing

This project is open to feature requests/suggestions, bug reports etc. via GitHub issues. Contribution and feedback are encouraged and always welcome. For more information about how to contribute, the project structure, as well as additional contribution information, see our Contribution Guidelines.

Security / Disclosure

If you find any bug that may be a security problem, please follow our instructions at in our security policy on how to report it. Please do not create GitHub issues for security-related doubts or problems.

Code of Conduct

We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone. By participating in this project, you agree to abide by its Code of Conduct at all times.

Licensing

Copyright 2024 SAP SE or an SAP affiliate company and sqlalchemy-hana contributors. Please see our LICENSE for copyright and license information. Detailed information including third-party components and their licensing/copyright information is available via the REUSE tool.

sqlalchemy-hana's People

Contributors

adadouche avatar bsrdjan avatar dependabot[bot] avatar i063960 avatar jarus avatar kasium avatar masterchen09 avatar monnemer avatar needoptimism avatar rakai93 avatar ralhei avatar sachdevas avatar sebastianwolf-sap avatar snnowwy avatar urangurang avatar yeongseon avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sqlalchemy-hana's Issues

Include readme on PyPI

The published release of sqlachemy-hana to PyPI currently has no project description (https://pypi.org/project/sqlalchemy-hana/). Maybe include the README.rst contents as the PyPI project description for future releases.

Also, the PyPI release is newer than the latest release here. Can the GitHub repo be updated to the latest release?

Support for JSON Document Store

Together with the SAP HANA 2.0 SPS 01 version, it was released the support for SAP HANA JSON Document Store (also known as DocStore or Document Store). Is there any plan to support this feature as part of the sqlalchemy-hana library?

Best regards,
Roberto Falk

Raise dedicated exception for LOCK_ACQUISITION_FAIL

If an application uses SQLAlchemy-HANA for serialized transaction updates with SELECT ... FOR UPDATE then you probably find something like the following in various places:

try:
    # SELECT ... WITH FOR UPDATE NOWAIT
except DBAPIError as exc:
    if exc.orig.errorcode == 146:
        # Special handling

A better and more reusable approach could be the introduction of a dedicated SQLAlchemy-HANA exception like RowLockAcquisitionError.

How to read LOB data from HANA tables using SQLAlchemy?

We are using sqlalchemy-hana to migrate data from one source hana system to another. We are not creating tables in target as it already exists. Data is retrieved from source as below,

_from sqlalchemy import create_engine, MetaData
engine = create_engine('hana://{0}:{1}@{2}:{3}'.format(username,password,host,port))
Metadata = MetaData(engine, schema=schema)
Metadata.reflect(bind=engine)

tableORM = Metadata.tables
table = tableORM[schema + '.'+tableName]
data = engine.execute(table.select()).fetchall()_

With the above code, data can be fetched from tables which doesn't contain LOB data types. But for tables with LOB, this read is getting stucked.
The module hdbcli has LOB class and it can be accessed via table cursor. How the same can be achieved in SQLAlchemy? Please give some suggestions.

Should Column store be the default?

Hi, I'm just working my way through a bunch of declarative code here adding 'hana_table_type' : 'Column'. Shouldn't column store be the default and you exclusively pick the row store? I'm pretty sure that most people would be using the column store as the row store is relatively featureless in comparison. Just a thought... :)

Regards,
Pat

Python Alembic migration tool with SAP HANA and sqlalchemy_hana

I'm porting an existing Python Flask web application from a Postgres RDBMS to a SAP HANA Express database.

SQLAlchemy works well with my SAP HANA database using the sqlalchemy_hana dialect.

However my Flask application uses the Alembic migration tool (based on SQLAlchemy) to manage the database.

Building the database using Alembic throws an "hana" KeyError.

This is actualy rather an Alembic issue than a sqlalchemy_hana issue and i therefore posted it on Stackoverflow inclusing a detailed error description.

However you may find many SQLAlchemy user managing thier database updates using Alembic migrations.

What is your oppinion on that?

Preview alembic dialect fails with alembic > 1.6

Creating a table fails with alembic > 1.6 and SA > 1.4

tests/conftest.py:116: in create_database_content
    command.upgrade(config, revision="head")
venv/lib/python3.7/site-packages/alembic/command.py:294: in upgrade
    script.run_env()
venv/lib/python3.7/site-packages/alembic/script/base.py:490: in run_env
    util.load_python_file(self.dir, "env.py")
venv/lib/python3.7/site-packages/alembic/util/pyfiles.py:97: in load_python_file
    module = load_module_py(module_id, path)
venv/lib/python3.7/site-packages/alembic/util/compat.py:182: in load_module_py
    spec.loader.exec_module(module)
my_project/migration/env.py:61: in <module>
    run_migrations_online()
my_project/migration/env.py:51: in run_migrations_online
    context.run_migrations()
venv/lib/python3.7/site-packages/alembic/runtime/environment.py:813: in run_migrations
    self.get_context().run_migrations(**kw)
venv/lib/python3.7/site-packages/alembic/runtime/migration.py:561: in run_migrations
    step.migration_fn(**kw)
my_project/migration/versions/f1894d2b7029_create_service_tables.py:26: in upgrade
    hana_table_type="COLUMN",
venv/lib/python3.7/site-packages/alembic/operations/ops.py:1072: in create_table
    return operations.invoke(op)
venv/lib/python3.7/site-packages/alembic/operations/base.py:354: in invoke
    return fn(self, operation)
venv/lib/python3.7/site-packages/alembic/operations/toimpl.py:101: in create_table
    operations.impl.create_table(table)
venv/lib/python3.7/site-packages/alembic/ddl/impl.py:277: in create_table
    self._exec(schema.CreateTable(table))
venv/lib/python3.7/site-packages/alembic/ddl/impl.py:146: in _exec
    return conn.execute(construct, multiparams)
venv/lib/python3.7/site-packages/sqlalchemy/engine/base.py:1262: in execute
    return meth(self, multiparams, params, _EMPTY_EXECUTION_OPTS)
venv/lib/python3.7/site-packages/sqlalchemy/sql/ddl.py:78: in _execute_on_connection
    self, multiparams, params, execution_options
venv/lib/python3.7/site-packages/sqlalchemy/engine/base.py:1350: in _execute_ddl
    dialect=dialect, schema_translate_map=schema_translate_map
venv/lib/python3.7/site-packages/sqlalchemy/sql/elements.py:522: in compile
    return self._compiler(dialect, **kw)
venv/lib/python3.7/site-packages/sqlalchemy/sql/ddl.py:29: in _compiler
    return dialect.ddl_compiler(dialect, self, **kw)
venv/lib/python3.7/site-packages/sqlalchemy/sql/compiler.py:455: in __init__
    self.string = self.process(self.statement, **compile_kwargs)
venv/lib/python3.7/site-packages/sqlalchemy/sql/compiler.py:490: in process
    return obj._compiler_dispatch(self, **kwargs)
venv/lib/python3.7/site-packages/sqlalchemy/sql/visitors.py:81: in _compiler_dispatch
    return meth(self, **kw)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

 

self = <sqlalchemy_hana.dialect.HANADDLCompiler object at 0x7fb4fcabf438>, create = <sqlalchemy.sql.ddl.CreateTable object at 0x7fb4fcabf3c8>

 

    def visit_create_table(self, create):
        table = create.element
    
        # The table._prefixes list outlives the current compilation, meaning changing the list
        # will change it globally. To prevent adding the same prefix multiple times, it is
        # removed again after the super-class'es visit_create_table call, which consumes the
        # table prefixes.
    
        table_type = table.kwargs.get('hana_table_type')
        appended_index = None
        if table_type:
>           appended_index = len(table._prefixes)
E           TypeError: object of type 'NoneType' has no len()

 

venv/lib/python3.7/site-packages/sqlalchemy_hana/dialect.py:169: TypeError

Release version 1.0

  • Cleanup open PRs
  • Cleanup open issues
  • Drop support for SQLAlchemy <= 1.2
  • Support SQLAlchemy 1.4 and 2.0
  • Drop support for python <= 3.7
  • Add support for python > 3.7
  • Add CI which includes checks and testing
  • Add checks: flake8, pylint, mypy, back and isort executed by pre-commit
  • Switch to pyproject.toml and drop setup.py/setup.cfg (by that remove VERSION,MANIFEST.in file)
  • Add py.typed file and mark is a typed
  • Move docs/ to readme
  • Remove pyhdb dialect
  • Cleanup gitignore
  • Remove License file in root dir
  • Only support modern alembic versions
  • Remove mock library dependency
  • Add hdbcli dependency to install-requires
  • Remove nox file
  • Switch to main branch
  • Add coverage and diff-coverage
  • Add missing code
  • Move requirements to test code
  • Fix The dbapi() classmethod on dialect classes has been renamed to import_dbapi
  • Introduce alembic dialect (support only modern alembic versions)
  • Add release action for publish on pypi
  • Add post-submit
  • Add pytest warnings
  • Remove base dialect; hana.hdbcli / hana+hdbcli
  • #87
  • Enable pytest strict markers
  • Update Readme (testing, dev setup, etc.)
  • Update changelog
  • Drop hidden feature auto_convert_lobs

Encrypted connection?

Hi, I can't seem to make it work when the HANA backend required SSL encryption.

Is it supported? Could you please provide an example?

Thanks.

Problem fetching NCLOB column

I connect to an SAP HANA Cloud database through hdbcli drivers, and I get the following stack trace:

Error: (-10419, ' Character value contains non-ASCII characters (8)')

The specific error arises from the call of the dbapi_cursor.fetchall().

After investigation, I found that the removal of the single column present in the query of type NCLOB makes the query successfull.
Indeed there are non-ASCII chars present in the column, but to my understanding the data type NCLOB should be able to have utf8 content.
I tried to set the parameter charset in the connection string, in the case there was any other charset defined by default, but I had no success in here.
The connection string uses hana+hdbcli; other queries with several data types, including NVARCHAR, work properly.

List package on `conda-forge`

Dear SAP team and maintainers @jarus,

Currently, sqlalchemy-hana is available from PyPi. This works fine for everyone managing their dependencies with pip but arguably excludes those working with conda (or makes their lives harder, at least; see also https://pythonspeed.com/articles/conda-vs-pip/).
It would be great if the package, ideally together with hdbcli due to dependencies, could also be listed on the conda-forge repository.

I'd be happy to help with preparing recipes and PRs for conda-forge (more information about the process can also be found here) - please feel free to reach out to me about this.
Thank you for your consideration. 🙏

Adding the Schema in the create_engine throws error

When adding the Schema in the create engine the connect method throws exception

engine = create_engine('hana://user:password@host:30015/VP1',echo=True);
connection = engine.connect()

It gives the error

TypeError: connect() got an unexpected keyword argument 'database'

If not the case how to pass the database Schema

authentication failed with LDAP user

I am trying to connect to a DB instance through LDAP validated credentials, but I continuously get an authentication failed exception.
The credentials work fine with another JAVA application, and password validated credentials work fine with python.
Is the error on the dialect level or on hdbcli? Is there any parameter that can be passed in order to make the connection successful?

Clenup testing suite

SQLAlchemy

  • computed_columns
  • computed_columns_stored
  • computed_columns_virtual
  • regexp_match
  • regexp_replace
  • precision_numerics_general
  • precision_numerics_enotation_small
  • precision_numerics_many_significant_digits
  • precision_numerics_retains_significant_digits
  • infinity_floats
  • float_or_double_precision_behaves_generically
  • emulated_lastrowid
  • dbapi_lastrowid
  • ctes
  • ctes_on_dml
  • ctes_with_update_delete
  • implicit_default_schema

Alembic

  • compute_*

Additional

  • enable disabled tests
  • Copy the whole requirement file and set what we need

Support SMALLDECIMAL and SHORTTEXT

/opt/virtualenv/lib/python3.7/site-packages/sqlalchemy_hana/dialect.py:410: SAWarning: Did not recognize type 'SMALLDECIMAL' of column 'retry_wait_seconds'
  row[1], column['name']
/opt/virtualenv/lib/python3.7/site-packages/sqlalchemy_hana/dialect.py:410: SAWarning: Did not recognize type 'SMALLDECIMAL' of column 'runtime_seconds'
  row[1], column['name']
/opt/virtualenv/lib/python3.7/site-packages/sqlalchemy_hana/dialect.py:410: SAWarning: Did not recognize type 'SMALLDECIMAL' of column 'elapsed_seconds'
  row[1], column['name']
/opt/virtualenv/lib/python3.7/site-packages/sqlalchemy_hana/dialect.py:410: SAWarning: Did not recognize type 'SMALLDECIMAL' of column 'collect_interval_sec'
  row[1], column['name']
/opt/virtualenv/lib/python3.7/site-packages/sqlalchemy_hana/dialect.py:410: SAWarning: Did not recognize type 'SMALLDECIMAL' of column 'collect_duration_sec'
  row[1], column['name']
/opt/virtualenv/lib/python3.7/site-packages/sqlalchemy_hana/dialect.py:410: SAWarning: Did not recognize type 'SMALLDECIMAL' of column 'refuse_seconds'
  row[1], column['name']

Column length not returned for NVARCHAR columns when inspecting a HANA Cloud view

Hi,
I am using sqlalchemy.engine.reflection.Inspector to relect view column of a database view in HANA Cloud.


inspector = reflection.Inspector.from_engine(engine)
colTypes = inspector.get_columns(table_name="myView", schema="mySchema")

while the view actually has fields id & user of data type NVARCHAR with length =5000 and 255 respectively, what I get for colTypes is

[{'name': 'id',
  'default': None,
  'nullable': True,
  'comment': None,
  'type': NVARCHAR()},
 {'name': 'user',
  'default': None,
  'nullable': True,
  'comment': None,
  'type': NVARCHAR()}]

The SQL generated is this (I inspected it with echo=True when creating the engine object):

SELECT COLUMN_NAME, DATA_TYPE_NAME, DEFAULT_VALUE, IS_NULLABLE, LENGTH, SCALE, COMMENTS FROM (
                   SELECT SCHEMA_NAME, TABLE_NAME, COLUMN_NAME, POSITION, DATA_TYPE_NAME, DEFAULT_VALUE, IS_NULLABLE,
                   LENGTH, SCALE, COMMENTS FROM SYS.TABLE_COLUMNS UNION ALL SELECT SCHEMA_NAME, VIEW_NAME AS TABLE_NAME,
                   COLUMN_NAME, POSITION, DATA_TYPE_NAME, DEFAULT_VALUE, IS_NULLABLE, LENGTH, SCALE, COMMENTS FROM
                   SYS.VIEW_COLUMNS) AS COLUMS WHERE SCHEMA_NAME=? AND TABLE_NAME=? ORDER BY POSITION

and it correctly contains the column length (see screenshot), but the information gets lost while passing it on to SQLAlchemy.
As a result, I cannot e.g. generate a table with same column type specification, since if I specify its column dtypes as NVARCHAR(), the default field length is 1, and my data will not fit

Actual view column types on DB
Capture

Result of relevant part of above SQL:
Capture2

Cloud Foundry Python Application calling on-premise HANA DB

I am trying to establish database connectivity to on-premise HANA DB, from a python cloud foundry application. Following steps have been done:

  1. TCP connection to on-premise HANA DB has been setup in SAP Cloud Connector (2.11.2). SAP cloud connector is a portable version installed on laptop, which is in the same network as HANA DB. Connection to SAP Cloud platform Sub-account is successful.

  2. A connectivity service instance has been created in the SAP Cloud platform.

  3. In the python application the database connection is being attempted using the following SQLALCHEMY - Create_engine statement:

create_engine('hana://DB_user:DB_pwd@virtual_host_from_scc/db',
connect_args = dict(host = 'virtual_host_from_scc',
port = ,
proxy_host = <on_premise_proxy_port from conn. service>,
proxy_port = <on_premise_socks5_proxy_port from conn.service>,
proxy_authorization=,
CONNECTTIMEOUT = 0,
sslvalidatecertificate = 'false'))

When the application is run with debug ON, errors out at the above statement. "sqlalchemy.exc.DBAPIError: (hdbcli.dbapi.Error) (-10709, "Connection failed (RTE:[89006] System call 'connect' failed, rc=110:Connection timed out")

If I pass the virtual host and port without connect_args, errors out saying it couldn't identify the virtual host.

Any pointers in this regard would be helpful.

Thanks.

Unable to connect to HANA via SSL

Hi,

I'm unable to connect to a HANA system which enforces SSL. I'm using encrypt as a parameter but doesn't seem to have any effect:

from sqlalchemy import create_engine
engine = create_engine('hana://user:pwd@host:portnr/tenant?encrypt=true');
connection = engine.connect()

DBAPIError: (hdbcli.dbapi.Error) (4321, 'only secure connections are allowed') (Background on this error at: http://sqlalche.me/e/dbapi)

Whereas I'm able to connect OK via the Python API using the encrypt option:

conn = dbapi.connect(
    address="host",
    port=portnr,
    encrypt="true",
    user="user",
    password="pwd"
)

Thanks,
Philip

sqlalchemy dialect for SAP R/3

Is there any plan to make a similar sqlalchemy dialect for connecting to SAP R/3 systems? Sorry that this isn't the most appropriate location to request this, but I couldn't find a better place. Maybe it could be built on top of PyRFC.

Alembic support

Hi, I've started implementation of hana support in alembic
michaelkuty/alembic@80427ef

I would like to ask you if you could help me with that because this is requirement for production usage.

My first problem is with table names because we have lowercased names

sqlalchemy.exc.DatabaseError: (pyhdb.exceptions.DatabaseError) invalid table name: FEATURES: line 13 col 40 (at pos 276) [SQL: '\nCREATE TABLE features (\n\tid INTEGER NOT NULL, \n\tcreated DATETIME, \n\tupdated DATETIME, \n\tcode VARCHAR(255), \n\tname VARCHAR(255), \n\tdescription CLOB, \n\trelease_date DATETIME, \n\tdepends_on_id INTEGER, \n\tPRIMARY KEY (id), \n\tUNIQUE (code), \n\tFOREIGN KEY(depends_on_id) REFERENCES features (id)\n)\n\n']

Existing tables not recognized when autogenerating migrations with alembic

Hi, I'm trying sqlalchemy-hana-1.0.0rc1 since I would like to use alembic to manage migrations in one of my projects. As I was playing with up/downgrades on the test system, I noticed the following behaviour:

After defining a few tables in my model, I autogenerated the first migration to create those in the DB. This worked fine and applying the migration resulted in the state shown in the first screenshot.

Screenshot 2023-11-09 at 13 11 27

However, if I generate another migration (without any changes to my model), alembic still claims that all tables are new:
Screenshot 2023-11-09 at 13 13 35

I have not experienced this behaviour with other DBs before, so I assume it's connected to the specific dialect, although I don't know for sure. It would be great if you could try to reproduce it.

TINYINT does not compile correctly

The code below seems to give an error and I can't figure out why myself. I wondered if you perhaps have any insights? It looks like it should run the "visit_TINYINT" method but somehow does not.

from sqlalchemy import Table, MetaData, Column
from sqlalchemy_hana.types import TINYINT
meta = MetaData()
t = Table('test_table', meta, Column('Col1', TINYINT))
str(t.columns['Col1'].type)

UnsupportedCompilationError: Compiler <sqlalchemy.sql.compiler.GenericTypeCompiler object at 0x0000000005C48CC0> can't render element of type <class 'sqlalchemy_hana.types.TINYINT'>

Support ARRAY type

Goal: support the SQLAlchemy HANA Array type

TODO

  • We cannot use the array tests, because HANA does not support multiple dimensions
  • Block multiple literal_round_trip
  • Check zero_indexes
  • Provide a rendering function (see below)

Rendering

    def visit_ARRAY(self, type_:types.ARRAY, **kw:Any) -> str:
        inner = self.process(type_.item_type, **kw)
        return f"{inner} ARRAY"

Example for secure encrypted connection

Hi there

Currently I'm not able to connect encrypted:
hana://user:[email protected]:1234/?encrypt=true&validateCertificate=false

What do I have to do?
Maybe you could add an example. (also to make sure, really use an encrypted connection).

Best
Michael

Excessive warning because of wrong value of supports_statement_cache

HANA dialect provider does not support statement caching, but not explicitly disable it, so the default caching supported is taken.

This causes excessive warnings like that in the application:

/usr/local/lib/python3.10/site-packages/upganalib/db/sql_lib.py:88: SAWarning: Dialect hana:hdbcli will not make use of SQL compilation caching as it does not set the 'supports_statement_cache' attribute to ``True``.  This can have significant performance implications including some performance degradations in comparison to prior SQLAlchemy versions.  Dialect maintainers should seek to set this attribute to True after appropriate development and testing for SQLAlchemy 1.4 caching support.   Alternatively, this attribute may be set to False which will disable this warning. (Background on this error at: https://sqlalche.me/e/14/cprf)

We are forced to "intervene" into the library and disable it locally in the application to suppress warnings:

from sqlalchemy_hana.dialect import HANAHDBCLIDialect

HANAHDBCLIDialect.supports_statement_cache = False

Could you please do it directly the library or support statement caching?

Our internal reference https://github.tools.sap/upgrade-analytics/upganalib/issues/57

Support for JWT?

Is there a way to authenticate to HANA using JWT? We are currently using basic authentication, but want to transition into integrating OAUTH because we don't want to enter username/password into the connection string manually for security reasons. How do we do this using SqlAlchemy? I've posted this question on Stackoverflow as well [1]

[1] Question on SO
[2] What we need to do

Insufficient Privilege Error during Engine Connection when using Account without Permissions to SYS.DUMMY

Line 305 in dilect.py throws a sqlalchemy.exc.DBAPIError error when I use a HANA Database account credentials to connect to HANA. For context, the HANA Database account does not have access to the DUMMY table in the SYS schema in our enterprise HANA server, but it does have access to other tables and views in the HANA environment. Can we update the sqlalchemy-hana code so that there is not a hard requirement to have access to the DUMMY table in order to connect to a given server and database?

from sqlalchemy import create_engine
engine = create_engine('hana://username:[email protected]:30015')  # where username and password are for a HANA database account
conn = engine.connect().execution_options(stream_results=True)

Full error:

sqlalchemy.exc.DBAPIError: (hdbcli.dbapi.Error) (258, "insufficient privilege: Detailed info for this error can be found with guid 'xxxxx')

Implement _get_server_version_info

a tuple containing a version number for the DB backend in use. This value is only available for supporting dialects, and is typically populated during the initial connection to the database.

    def _get_server_version_info(self, connection):
        result = connection.execute(
            sql.text(
                "SELECT VERSION FROM SYS.M_DATABASE"
            )
        )
        return tuple([int(i) for i in result.scalar().split('.')])

Alembic add constraints ignores check constraints

The alembic dialect contains

def add_constraint(self, const):
	if isinstance(const, schema.CheckConstraint):
		# HANA doesn't support check constraints
		return
	super().add_constraint(const)`

But check constraints are supported in HANA

UnicodeDecodeError

I am receiving the following error:
UnicodeDecodeError: 'utf8' codec can't decode byte 0xed in position 259: invalid continuation byte

Is there a way to specify the encoding/charset?

Error while using sqlalchemy-hana

Hi,
I am trying to execute the below line

engine = create_engine('hana://SYSTEM:[email protected]:30015')

And i get the below error--

NoSuchModuleError: Can't load plugin: sqlalchemy.dialects:hana

I am not able to understand the error, any idea?

Support of autoincremented key

Sqlalchemy-hana should detect an autoincremented primary key when using session.add(attribute1, attribute2...) and be able to insert the attributes without specifing the value of the primary key.

sql to create table:
CREATE COLUMN TABLE "SCHEME"."TABLE"("ID" BIGINT NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, "ATTRIBUTE1" TYPE, "ATTRIBUTE2" TYPE...);

Dropping primary key fails with alembic dialect

HANA has a special syntax for dropping a primary key: ALTER TABLE XXX DROP PRIMARY KEY.
The alembic dialect currently doesn't take care of this and build a ALTER TABLE DROP CONSTRAINT statement.

Regex based sql queries

Are regex based SQL queries supported by sqlalchemy-hana?
what should be the appropriate syntax for executing this SQL query and getting back the resulting pandas dataframe?
SELECT ATTRIBUTE_VALUE FROM PARSING2 WHERE (ATTRIBUTE_NAME NOT REGEXP "^ADDITIONAL")

hdbcli>2.5 cannot bind parameters by name (Python)

Hi there

Because this, it's also not possible to do this with sql-alchemy (hana).

Since the hdbcli>=2.5 it's not possible to bind parameters by name.
In version hdbcli==2.4.202 it's still working.
With unnamed params it's working (using of ?).
I'm using conda with python 3.8 on windows.

I tested the behaviour with:

  • 2.5.111
  • 2.6.64

Minimal example:

from hdbcli import dbapi

conn = dbapi.connect(...)
cursor = conn.cursor()

sql = 'SELECT count(*) FROM sys.tables where table_name like :xx'
params = {
    'xx': '%'
}

cursor.execute(sql, params)
for row in cursor:
    print(row)

Error message:

SystemError: d:\bld\python-split_1602094606092\work\objects\dictobject.c:2469: bad argument to internal function

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "S:/cdwh-prefect/test.py", line 12, in <module>
    cursor.execute(sql, params)
SystemError: <built-in method parsenamedquery of pyhdbcli.Cursor object at 0x0000026BB89892F0> returned a result with an error set

Also posted here with no answer:
https://answers.sap.com/questions/13209720/hdbcli25-cannot-bind-parameters-by-name-python.html

SQLAlchemy 1.4 support

Dear Team,

According to the SQLAlchemy official site, the dialect sqlalchemy-hana is only supported until version 1.3. Since the version 1.3 is now in Maintenance and hence (I guess) it will change to EOL as soon as 2.0 is officially released, I was wondering whether you have any plan to support SQLAlchemy 1.4 in the near future?

The question originated from the fact that I am right now pondering the usage of SQLAlchemy as a foundation ORM library that connects to our SAP HANA database installation base for automating certain core technical processes in my company via Ansible. Since, as you may know, SQLAlchemy 1.4 brings new features that are recommended to be used in order to be Version 2.0 ready, I think it would be the right time for me to start that development based on version 1.4 instead of 1.3. Hence my question here.

Any light you can shed on that, it will be appreciated.

Thanks!
Jose M. Prieto

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.