【问题标题】:Django inspectdb issue using Oracle database使用 Oracle 数据库的 Django inspectdb 问题
【发布时间】:2011-06-22 08:02:40
【问题描述】:

安装了 cx_oracle 并运行了 inspectdb。似乎没有任何输出?有人可以帮忙吗?在 Oracle 中使用 inspectdb 是否存在已知问题?

下面是命令和settings.py。

python manage.py inspectdb --database xxx_db

# This is an auto-generated Django model module.
# You'll have to do the following manually to clean this up:
#     * Rearrange models' order
#     * Make sure each model has one field with primary_key=True
# Feel free to rename the models, but don't rename db_table values or field names.
#
# Also note: You'll have to insert the output of 'django-admin.py sqlcustom [appname]'
# into your database.

from django.db import models

settings.py

DATABASES = {
    'xxx_db': {
        'ENGINE': 'django.db.backends.oracle',
        'NAME': 'abc',
        'USER': 'abc_read',
        'PASSWORD': 'abc_read',
        'HOST': 'apps.domain.com',
        'PORT': 'xxxx'
        },
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'aaaa',
        'USER': 'aaaa',
        'PASSWORD': 'xxxx',
        'HOST': '/tmp/mysql.sock',
        'PORT': ''
        }
}

【问题讨论】:

  • 您使用的是什么版本的oraclecx_oracle
  • Reiner - cx_Oracle-5.0.3 和 Oracle 10.2.0.4.0

标签: python database django oracle inspectdb


【解决方案1】:

两件事:

  1. Inspectdb 不正式支持 oracle(请参阅 Django Docs - inspectdb )悲伤时期。
  2. Django 对 Oracle 模式的支持不是很强(请参阅unresolved Django ticket 6148)因此,如果您能够使用模式的主用户进行连接,那么您可能会有更好的运气,使您希望内省的模式成为默认模式.

我能够通过更改 introspection.py 中的选择来获得基本模型文件输出。对我来说,我将 django/db/backends/oracle/introspection.py (第 40 行左右)中的 get_table_list 函数从:

def get_table_list(self, cursor):
    "Returns a list of table names in the current database."
    cursor.execute("SELECT TABLE_NAME FROM USER_TABLES")
    return [row[0].lower() for row in cursor.fetchall()]

def get_table_list(self, cursor):
    "Returns a list of table names in the current database."
    cursor.execute("SELECT TABLE_NAME FROM ALL_TABLES WHERE OWNER = 'SCHEMA_TO_QUERY'")
    return [row[0].lower() for row in cursor.fetchall()]

但是当我读到 Oracle 中对模式的整体支持很差时,我放弃了 django

【讨论】:

  • 这两个查询在语法上是相同的。根据定义user_tables 只包含用户拥有的表的定义。另外,myschema 应该是大写的......
  • @Ben 正确,刚刚编辑以查询 ALL_TABLES 并将架构名称更改为更具描述性。谢谢
【解决方案2】:

它对我有用。

您是否检查了用户有权查看 Oracle 中的所有表?

不管怎样,我很好奇 inspectdb 使用的 SQL 是什么。

【讨论】:

    【解决方案3】:

    @Plecebo 走在正确的轨道上。 get_table_list 方法是问题的根源,但是给出的 Select 语句不起作用。

    我暂时对表名进行了硬编码,以便快速获得所需内容:

    (django/db/backends/oracle/introspection.py 第 40 行)

    def get_table_list(self, cursor):
        "Returns a list of table names in the current database."
        #cursor.execute("SELECT TABLE_NAME FROM USER_TABLES")
        return ['table_name1', 'table_name2']
        #return [row[0].lower() for row in cursor.fetchall()]
    

    【讨论】:

      【解决方案4】:

      当我最近搜索使用 Django 2.0 和我正在使用的 Oracle 11g 遗留数据库整理 inspectdb 时出现了这个问题,我已经开始修复内省,到目前为止我已经设法得到修改/lib/python3.6/site-packages/django/db/backends/oracle/introspection.py后基本表的输出: (本质上将所有 user_* 表替换为 all_* 表)

      我目前的工作解决方案是下面的文件内容 (introspection.py)。

      import warnings
      from collections import namedtuple
      
      import cx_Oracle
      
      from django.db import models
      from django.db.backends.base.introspection import (
          BaseDatabaseIntrospection, FieldInfo as BaseFieldInfo, TableInfo,
      )
      from django.utils.deprecation import RemovedInDjango21Warning
      
      FieldInfo = namedtuple('FieldInfo', BaseFieldInfo._fields + ('is_autofield',))
      
      
      class DatabaseIntrospection(BaseDatabaseIntrospection):
          # Maps type objects to Django Field types.
          data_types_reverse = {
              cx_Oracle.BLOB: 'BinaryField',
              cx_Oracle.CLOB: 'TextField',
              cx_Oracle.DATETIME: 'DateField',
              cx_Oracle.FIXED_CHAR: 'CharField',
              cx_Oracle.FIXED_NCHAR: 'CharField',
              cx_Oracle.NATIVE_FLOAT: 'FloatField',
              cx_Oracle.NCHAR: 'CharField',
              cx_Oracle.NCLOB: 'TextField',
              cx_Oracle.NUMBER: 'DecimalField',
              cx_Oracle.STRING: 'CharField',
              cx_Oracle.TIMESTAMP: 'DateTimeField',
          }
      
          cache_bust_counter = 1
      
          def get_field_type(self, data_type, description):
              if data_type == cx_Oracle.NUMBER:
                  precision, scale = description[4:6]
                  if scale == 0:
                      if precision > 11:
                          return 'BigAutoField' if description.is_autofield else 'BigIntegerField'
                      elif precision == 1:
                          return 'BooleanField'
                      elif description.is_autofield:
                          return 'AutoField'
                      else:
                          return 'IntegerField'
                  elif scale == -127:
                      return 'FloatField'
      
              return super().get_field_type(data_type, description)
      
          def get_table_list(self, cursor):
              """Return a list of table and view names in the current database."""
              # cursor.execute("SELECT TABLE_NAME, 't' FROM USER_TABLES UNION ALL "
              #                "SELECT VIEW_NAME, 'v' FROM USER_VIEWS")
              cursor.execute("SELECT TABLE_NAME, 't' FROM ALL_TABLES WHERE OWNER = 'V500' ")
              return [TableInfo(row[0].lower(), row[1]) for row in cursor.fetchall()]
      
          def get_table_description(self, cursor, table_name):
              """
              Return a description of the table with the DB-API cursor.description
              interface.
              """
              cursor.execute("""
                  SELECT
                      column_name,
                      data_default,
                      CASE
                          WHEN char_used IS NULL THEN data_length
                          ELSE char_length
                      END as internal_size,
                      0 as is_autofield
                  FROM ALL_TAB_COLUMNS
                  WHERE table_name = UPPER(%s)""", [table_name])
              field_map = {
                  column: (internal_size, default if default != 'NULL' else None, is_autofield)
                  for column, default, internal_size, is_autofield in cursor.fetchall()
              }
              self.cache_bust_counter += 1
              cursor.execute("SELECT * FROM {} WHERE ROWNUM < 2 AND {} > 0".format(
                  self.connection.ops.quote_name(table_name),
                  self.cache_bust_counter))
              description = []
              for desc in cursor.description:
                  name = desc[0]
                  internal_size, default, is_autofield = field_map[name]
                  name = name % {}  # cx_Oracle, for some reason, doubles percent signs.
                  description.append(FieldInfo(*(
                      (name.lower(),) +
                      desc[1:3] +
                      (internal_size, desc[4] or 0, desc[5] or 0) +
                      desc[6:] +
                      (default, is_autofield)
                  )))
              return description
      
          def table_name_converter(self, name):
              """Table name comparison is case insensitive under Oracle."""
              return name.lower()
      
          def get_sequences(self, cursor, table_name, table_fields=()):
              # Tables don't exist in 11g (this function added in django 2
              # cursor.execute("""
              #     SELECT
              #         user_tab_identity_cols.sequence_name,
              #         user_tab_identity_cols.column_name
              #     FROM
              #         user_tab_identity_cols,
              #         user_constraints,
              #         user_cons_columns cols
              #     WHERE
              #         user_constraints.constraint_name = cols.constraint_name
              #         AND user_constraints.table_name = user_tab_identity_cols.table_name
              #         AND cols.column_name = user_tab_identity_cols.column_name
              #         AND user_constraints.constraint_type = 'P'
              #         AND user_tab_identity_cols.table_name = UPPER(%s)
              # """, [table_name])
              # # Oracle allows only one identity column per table.
              # row = cursor.fetchone()
              # if row:
              #     return [{'name': row[0].lower(), 'table': table_name, 'column': row[1].lower()}]
              # # To keep backward compatibility for AutoFields that aren't Oracle
              # # identity columns.
              # for f in table_fields:
              #     if isinstance(f, models.AutoField):
              #         return [{'table': table_name, 'column': f.column}]
              return []
      
          def get_relations(self, cursor, table_name):
              """
              Return a dictionary of {field_name: (field_name_other_table, other_table)}
              representing all relationships to the given table.
              """
              table_name = table_name.upper()
              cursor.execute("""
          SELECT ca.column_name, cb.table_name, cb.column_name
          FROM   ALL_CONSTRAINTS, ALL_CONS_COLUMNS ca, ALL_CONS_COLUMNS cb
          WHERE  ALL_CONSTRAINTS.table_name = %s AND
                 ALL_CONSTRAINTS.constraint_name = ca.constraint_name AND
                 ALL_CONSTRAINTS.r_constraint_name = cb.constraint_name AND
                 ca.position = cb.position""", [table_name])
      
              relations = {}
              for row in cursor.fetchall():
                  relations[row[0].lower()] = (row[2].lower(), row[1].lower())
              return relations
      
          def get_key_columns(self, cursor, table_name):
              cursor.execute("""
                  SELECT ccol.column_name, rcol.table_name AS referenced_table, rcol.column_name AS referenced_column
                  FROM ALL_CONSTRAINTS c
                  JOIN ALL_CONS_COLUMNS ccol
                    ON ccol.constraint_name = c.constraint_name
                  JOIN ALL_CONS_COLUMNS rcol
                    ON rcol.constraint_name = c.r_constraint_name
                  WHERE c.table_name = %s AND c.constraint_type = 'R'""", [table_name.upper()])
              return [tuple(cell.lower() for cell in row)
                      for row in cursor.fetchall()]
      
          def get_indexes(self, cursor, table_name):
              warnings.warn(
                  "get_indexes() is deprecated in favor of get_constraints().",
                  RemovedInDjango21Warning, stacklevel=2
              )
              sql = """
          SELECT LOWER(uic1.column_name) AS column_name,
                 CASE ALL_CONSTRAINTS.constraint_type
                     WHEN 'P' THEN 1 ELSE 0
                 END AS is_primary_key,
                 CASE ALL_INDEXES.uniqueness
                     WHEN 'UNIQUE' THEN 1 ELSE 0
                 END AS is_unique
          FROM   ALL_CONSTRAINTS, ALL_INDEXES, ALL_IND_COLUMNS uic1
          WHERE  ALL_CONSTRAINTS.constraint_type (+) = 'P'
            AND  ALL_CONSTRAINTS.index_name (+) = uic1.index_name
            AND  ALL_INDEXES.uniqueness (+) = 'UNIQUE'
            AND  ALL_INDEXES.index_name (+) = uic1.index_name
            AND  uic1.table_name = UPPER(%s)
            AND  uic1.column_position = 1
            AND  NOT EXISTS (
                    SELECT 1
                    FROM   ALL_IND_COLUMNS uic2
                    WHERE  uic2.index_name = uic1.index_name
                      AND  uic2.column_position = 2
                 )
              """
              cursor.execute(sql, [table_name])
              indexes = {}
              for row in cursor.fetchall():
                  indexes[row[0]] = {'primary_key': bool(row[1]),
                                     'unique': bool(row[2])}
              return indexes
      
          def get_constraints(self, cursor, table_name):
              """
              Retrieve any constraints or keys (unique, pk, fk, check, index) across
              one or more columns.
              """
              constraints = {}
              # Loop over the constraints, getting PKs, uniques, and checks
              cursor.execute("""
                  SELECT
                      ALL_CONSTRAINTS.constraint_name,
                      LISTAGG(LOWER(cols.column_name), ',') WITHIN GROUP (ORDER BY cols.position),
                      CASE ALL_CONSTRAINTS.constraint_type
                          WHEN 'P' THEN 1
                          ELSE 0
                      END AS is_primary_key,
                      CASE
                          WHEN ALL_CONSTRAINTS.constraint_type IN ('P', 'U') THEN 1
                          ELSE 0
                      END AS is_unique,
                      CASE ALL_CONSTRAINTS.constraint_type
                          WHEN 'C' THEN 1
                          ELSE 0
                      END AS is_check_constraint
                  FROM
                      ALL_CONSTRAINTS
                  LEFT OUTER JOIN
                      ALL_CONS_COLUMNS cols ON ALL_CONSTRAINTS.constraint_name = cols.constraint_name
                  WHERE
                      ALL_CONSTRAINTS.constraint_type = ANY('P', 'U', 'C')
                      AND ALL_CONSTRAINTS.table_name = UPPER(%s)
                  GROUP BY ALL_CONSTRAINTS.constraint_name, ALL_CONSTRAINTS.constraint_type
              """, [table_name])
              for constraint, columns, pk, unique, check in cursor.fetchall():
                  constraints[constraint] = {
                      'columns': columns.split(','),
                      'primary_key': pk,
                      'unique': unique,
                      'foreign_key': None,
                      'check': check,
                      'index': unique,  # All uniques come with an index
                  }
              # Foreign key constraints
              cursor.execute("""
                  SELECT
                      cons.constraint_name,
                      LISTAGG(LOWER(cols.column_name), ',') WITHIN GROUP (ORDER BY cols.position),
                      LOWER(rcols.table_name),
                      LOWER(rcols.column_name)
                  FROM
                      ALL_CONSTRAINTS cons
                  INNER JOIN
                      ALL_CONS_COLUMNS rcols ON rcols.constraint_name = cons.r_constraint_name AND rcols.position = 1
                  LEFT OUTER JOIN
                      ALL_CONS_COLUMNS cols ON cons.constraint_name = cols.constraint_name
                  WHERE
                      cons.constraint_type = 'R' AND
                      cons.table_name = UPPER(%s)
                  GROUP BY cons.constraint_name, rcols.table_name, rcols.column_name
              """, [table_name])
              for constraint, columns, other_table, other_column in cursor.fetchall():
                  constraints[constraint] = {
                      'primary_key': False,
                      'unique': False,
                      'foreign_key': (other_table, other_column),
                      'check': False,
                      'index': False,
                      'columns': columns.split(','),
                  }
              # Now get indexes
              cursor.execute("""
                  SELECT
                      ind.index_name,
                      LOWER(ind.index_type),
                      LISTAGG(LOWER(cols.column_name), ',') WITHIN GROUP (ORDER BY cols.column_position),
                      LISTAGG(cols.descend, ',') WITHIN GROUP (ORDER BY cols.column_position)
                  FROM
                      ALL_IND_COLUMNS cols, ALL_INDEXES ind
                  WHERE
                      cols.table_name = UPPER(%s) AND
                      NOT EXISTS (
                          SELECT 1
                          FROM ALL_CONSTRAINTS cons
                          WHERE ind.index_name = cons.index_name
                      ) AND cols.index_name = ind.index_name
                  GROUP BY ind.index_name, ind.index_type
              """, [table_name])
              for constraint, type_, columns, orders in cursor.fetchall():
                  constraints[constraint] = {
                      'primary_key': False,
                      'unique': False,
                      'foreign_key': None,
                      'check': False,
                      'index': True,
                      'type': 'idx' if type_ == 'normal' else type_,
                      'columns': columns.split(','),
                      'orders': orders.split(','),
                  }
              return constraints
      

      注意到 Django 1.11 是最后一个正式支持 11g 的版本,这是我从 How to make Django 2.0 to use Oracle 11g syntax instead of 12c? 那里得到的,这促使我查看 1.11 代码库以了解发生了什么变化 (https://github.com/django/django/blob/stable/1.11.x/django/db/backends/oracle/introspection.py)

      目前我正在从只读数据库连接读取数据,因此修复迁移不是我目前的动力。在编写inspectdb 函数时,将6000 多个表作为python 代码输出,没有任何重大问题。

      【讨论】:

        【解决方案5】:

        Haaaa.. 刚面对这个问题,找到了一个愚蠢的理由!!

        无需像其中一篇文章那样编辑任何底层文件。当您在该数据库中没有表时,您将面临此问题。呵呵。。

        Create a few tables and then try。它就像一个魅力。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-10-01
          • 2018-08-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-05-30
          • 1970-01-01
          相关资源
          最近更新 更多