2

我目前正在为一个新项目评估 SQLAlchemy。在尝试反映现有数据库(postgres、oracle)的表时,我遇到了一些问题。

为了连接到 Oracle 数据库,正在使用以下代码:

from sqlalchemy import create_engine, MetaData, Table, inspect, select
import pandas as pd
import keyring

dbtype = 'Oracle'
dbenv = 'LOCAL'
dbname = 'MYDB'
dbsys = '%s%s' % (dbtype, dbenv)
dbusr = 'myusr'
dbpwd = keyring.get_password(dbsys, dbusr)
dbhost = 'mydbhost'
dbport = 1521

dbconstr = 'oracle+cx_oracle://%s:%s@%s:%s/%s' % (dbusr, dbpwd, dbhost, dbport, dbname)
engine = create_engine(dbconstr)

在对如何组装连接字符串(tnsnames.ora我的系统上没有)进行了一些尝试和错误之后,我成功地建立了与远程 Oracle 数据库的连接。

在检查数据库并尝试检索可用表的名称时,我收到了一个空列表。

inspector = inspect(engine)
instbls = inspector.get_table_names()
print('instbls: %s' % instbls)

# instbls: []

我实际上期望的是系统表或公共表会被列出。

接下来我尝试反映模式的数据库表SYS

md01 = MetaData(bind=engine, schema=['SYS', 'SYSTEM'], reflect=True)
# Warning: sqlalchemy_oracle.py:37: SADeprecationWarning: The MetaData.reflect flag is deprecated and will be removed in a future release.   Please use the MetaData.reflect() method.

print('type(md01): %s' % type(md01))
# type(md01): <class 'sqlalchemy.sql.schema.MetaData'>

md01tbls = md01.tables
print('md01tbls: %s' % md01tbls)
# md01tbls: immutabledict({})

所以我相应地修改了代码以消除警告

md02 = MetaData(bind=engine, schema=['SYS', 'SYSTEM']).reflect()
print('type(md02): %s' % type(md02))
# type(md02): <class 'NoneType'>

md02tbls = md02.tables
# Exception: Traceback (most recent call last):
#    ...
# File "sqlalchemy_oracle.py", line 44, in <module>
# AttributeError: 'NoneType' object has no attribute 'tables'

print('md02tbls: %s' % md02tbls)

没有警告,而是一个例外。:(

在对网络进行了一些研究之后,似乎需要进行一些进一步的代码修改。

md03 = MetaData().reflect(bind=engine, schema=['SYS', 'SYSTEM'])
# Exception: Traceback (most recent call last):
#    ...
# File "/site-packages/sqlalchemy/engine/default.py", line 637, in denormalize_name
#    name_lower = name.lower()
# AttributeError: 'list' object has no attribute 'lower'

print('type(md03): %s' % type(md03))
md03tbls = md03.tables
print('md03tbls: %s' % md03tbls)

甚至不如之前的尝试成功。将列出的模式名称转换为小写字母会导致相同的结果。

所以也许reflect只能处理strings而没有lists

md05a = MetaData().reflect(bind=engine, schema='sys')
print('type(md05a): %s' % type(md05a))
# type(md05a): <class 'NoneType'>

md05b = MetaData().reflect(bind=engine, schema='system')
print('type(md05b): %s' % type(md05b))
# type(md05b): <class 'NoneType'>

md05atbls = md05a.tables
# Exception: Traceback (most recent call last):
#    ...
# File "sqlalchemy_oracle.py", line 44, in <module>
# AttributeError: 'NoneType' object has no attribute 'tables'

print('md05atbls: %s' % md05atbls)
md05btbls = md05b.tables
print('md05btbls: %s' % md05btbls)

那里也没有运气!

也许无法反映系统表?所以让我们尝试用户特定的对象。

md06 = MetaData().reflect(bind=engine, schema='MYUSR')
print('type(md06): %s' % type(md06))
# type(md06): <class 'NoneType'>

md06tbls = md06.tables
# Exception: Traceback (most recent call last):
#    ...
# File "sqlalchemy_oracle.py", line 44, in <module>
# AttributeError: 'NoneType' object has no attribute 'tables'

print('md06tbls: %s' % md06tbls)

再次是已知的例外。

回到不推荐使用的调用版本。

md07 = MetaData(bind=engine, schema=['MYUSR', 'MYUSR2'], reflect=True)
print('type(md07): %s' % type(md07))
# type(md07): <class 'sqlalchemy.sql.schema.MetaData'>

md07tbls = md07.tables
print('md07tbls: %s' % md07tbls)
# md07tbls: immutabledict({})

也不例外,但也没有表,尽管在模式MYUSRMYUSR2.

指定架构时拆分list为。strings

md08a = MetaData(bind=engine, schema='MYUSR', reflect=True)
# Warning: sqlalchemy_oracle.py:37: SADeprecationWarning: The MetaData.reflect flag is deprecated and will be removed in a future release.   Please use the MetaData.reflect() method.

print('type(md08a): %s' % type(md08a))
# ype(md08a): <class 'sqlalchemy.sql.schema.MetaData'>

md08b = MetaData(bind=engine, schema='MYUSR2', reflect=True)
# Warning: sqlalchemy_oracle.py:40: SADeprecationWarning: The MetaData.reflect flag is deprecated and will be removed in a future release.   Please use the MetaData.reflect() method.

print('type(md08b): %s' % type(md08b))
# type(md08b): <class 'sqlalchemy.sql.schema.MetaData'>

md08atbls = md08a.tables
print('md08atbls: %s' % md08atbls)
# md08atbls: immutabledict({'MYUSR.table01': Table(...)})

md08btbls = md08b.tables
print('md08btbls: %s' % md08btbls)
# md08btbls: immutabledict({'MYUSR2.table01': Table(...)})

所以这次尝试最终产生了预期的结果。:)

从这一切看来,

  1. 无法反映系统/公共表(数据库对象);
  2. 只能反映用户特定的表;
  3. 只有不推荐使用的反射版本有效;
  4. 在 Oracle 和 PostgreSQL 数据库中都可以观察到这种行为。

有什么我忽略的吗?我有什么误解?做错了,与实际意图不同?

我的环境包括

  • Ubuntu linux 16.04LTS;
  • 蟒蛇3.8;
  • SQLAlchemy 1.3.16;
  • cx_Oracle 7.3.0;
  • psycopg2 2.8.5;
  • 本地 Oracle 18c Instant 客户端;
  • 远程 Oracle 19c 数据库;
  • 本地 PostgreSQL 9.5 数据库。
4

2 回答 2

2

作为@snakecharmerb 答案的补充,这对我来说适用于 SQLAlchemy 1.4:

connection_uri = (
    "oracle+cx_oracle://scott:tiger@192.168.0.199:1521/"
    "?service_name=xepdb1&encoding=UTF-8&nencoding=UTF-8"
)

engine = sa.create_engine(connection_uri)

insp = sa.inspect(engine)
print(insp.default_schema_name)
# scott
tables = insp.get_table_names()
print(tables)
# ['table1']
tables = insp.get_table_names(schema="sys")
print(tables)
# ['sqllog$', 'sqlobj$', 'sqlobj$data', 'svcobj$', …
于 2021-09-25T13:10:17.837 回答
2

对于 Postgres,您可以像这样在数据库的pg_catalog模式中反映表:

>>> import sqlalchemy as sa
>>> engine = sa.create_engine('postgresql:///postgres', echo=False)
>>> meta = sa.MetaData()
>>> meta.reflect(engine, schema='pg_catalog')
>>> meta.tables.keys()
(long sequence of table names...)

设置将echo=Truecreate_engine构建元数据时输出 SQLAlchemy 对系统表执行的所有查询,这可能有助于对其他数据库进行反向工程。

于 2021-09-25T09:51:05.847 回答