<reponame>dyna-mis/Hilabeling<filename>python/plugins/db_manager/db_plugins/oracle/connector.py
# -*- coding: utf-8 -*-

"""
/***************************************************************************
Name                 : DB Manager
Description          : Database manager plugin for QGIS (Oracle)
Date                 : Aug 27, 2014
copyright            : (C) 2014 by <NAME>
email                : <EMAIL>

The content of this file is based on
- PG_Manager by <NAME> <<EMAIL>> (GPLv2 license)
- DB Manager by <NAME> <<EMAIL>> (GPLv2 license)
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program 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 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
"""
from builtins import str
from builtins import range

from qgis.PyQt.QtSql import QSqlDatabase

from ..connector import DBConnector
from ..plugin import ConnectionError, DbError, Table

import os
from qgis.core import Qgis, QgsApplication, NULL, QgsWkbTypes
from . import QtSqlDB
import sqlite3

from functools import cmp_to_key


def classFactory():
    if QSqlDatabase.isDriverAvailable("QOCISPATIAL"):
        return OracleDBConnector
    else:
        return None


class OracleDBConnector(DBConnector):

    ORGeomTypes = {
        2001: QgsWkbTypes.Point,
        2002: QgsWkbTypes.LineString,
        2003: QgsWkbTypes.Polygon,
        2005: QgsWkbTypes.MultiPoint,
        2006: QgsWkbTypes.MultiLineString,
        2007: QgsWkbTypes.MultiPolygon,
        3001: QgsWkbTypes.Point25D,
        3002: QgsWkbTypes.LineString25D,
        3003: QgsWkbTypes.Polygon25D,
        3005: QgsWkbTypes.MultiPoint25D,
        3006: QgsWkbTypes.MultiLineString25D,
        3007: QgsWkbTypes.MultiPolygon25D
    }

    def __init__(self, uri, connName):
        DBConnector.__init__(self, uri)

        self.connName = connName
        self.user = uri.username() or os.environ.get('USER')
        self.passwd = uri.password()
        self.host = uri.host()

        if self.host != "":
            self.dbname = self.host
            if uri.port() != "" and uri.port() != "1521":
                self.dbname += ":" + uri.port()
            if uri.database() != "":
                self.dbname += "/" + uri.database()
        elif uri.database() != "":
            self.dbname = uri.database()

        # Connection options
        self.useEstimatedMetadata = uri.useEstimatedMetadata()
        self.userTablesOnly = uri.param('userTablesOnly').lower() == "true"
        self.geometryColumnsOnly = uri.param(
            'geometryColumnsOnly').lower() == "true"
        self.allowGeometrylessTables = uri.param(
            'allowGeometrylessTables').lower() == "true"
        self.onlyExistingTypes = uri.param(
            'onlyExistingTypes').lower() == "true"
        self.includeGeoAttributes = uri.param(
            'includeGeoAttributes').lower() == "true"

        # For refreshing
        self.populated = False
        try:
            self.connection = QtSqlDB.connect(
                "QOCISPATIAL", self.dbname, self.user, self.passwd)

        except self.connection_error_types() as e:
            raise ConnectionError(e)

        # Find if we can connect to data_sources_cache.db
        sqlite_cache_file = os.path.join(
            QgsApplication.qgisSettingsDirPath(), u"data_sources_cache.db")
        if (os.path.isfile(sqlite_cache_file)):
            try:
                self.cache_connection = sqlite3.connect(sqlite_cache_file)
            except sqlite3.Error:
                self.cache_connection = False

        # Find if there is cache for our connection:
        if self.cache_connection:
            try:
                cache_c = self.cache_connection.cursor()
                query = (u"SELECT COUNT(*) FROM meta_oracle WHERE"
                         u" conn = '{}'".format(self.connName))
                cache_c.execute(query)
                has_cached = cache_c.fetchone()[0]
                cache_c.close()
                if not has_cached:
                    self.cache_connection = False

            except sqlite3.Error:
                self.cache_connection = False

        self._checkSpatial()
        self._checkGeometryColumnsTable()

    def _connectionInfo(self):
        return str(self._uri.connectionInfo(True))

    def _checkSpatial(self):
        """Check whether Oracle Spatial is present in catalog."""
        query = (u"SELECT count(*) FROM v$option WHERE parameter = "
                 u" 'Spatial' AND value = 'TRUE'")
        c = self._execute(None, query)
        self.has_spatial = self._fetchone(c)[0] > 0
        c.close()

        return self.has_spatial

    def _checkGeometryColumnsTable(self):
        """Check if user can read *_SDO_GEOM_METADATA view."""
        # First check if user can read ALL_SDO_GEOM_METADATA
        privs = self.getRawTablePrivileges('ALL_SDO_GEOM_METADATA',
                                           'MDSYS', 'PUBLIC')
        # Otherwise, try with USER_SDO_GEOM_METADATA
        if not privs[0]:
            privs = self.getRawTablePrivileges('USER_SDO_GEOM_METADATA',
                                               'MDSYS', 'PUBLIC')

        if privs[0]:
            self.has_geometry_columns = True
            self.has_geometry_columns_access = True
            self.is_geometry_columns_view = True
            return True
        else:
            self.has_geometry_columns = False
            self.has_geometry_columns_access = False
            self.is_geometry_columns_view = False
            return False

    def getInfo(self):
        """Returns Oracle Database server version."""
        c = self._execute(None, u"SELECT * FROM V$VERSION WHERE ROWNUM < 2")
        res = self._fetchone(c)
        c.close()
        return res

    def hasCache(self):
        """Returns self.cache_connection."""
        if self.cache_connection:
            return True
        return False

    def getSpatialInfo(self):
        """Returns Oracle Spatial version."""
        if not self.has_spatial:
            return

        try:
            c = self._execute(None, u"SELECT SDO_VERSION FROM DUAL")
        except DbError:
            return
        res = self._fetchone(c)
        c.close()

        return res

    def hasSpatialSupport(self):
        """Find if there is Spatial support."""
        return self.has_spatial

    def hasRasterSupport(self):
        """No raster support for the moment!"""
        # return self.has_raster
        return False

    def hasCustomQuerySupport(self):
        """From Qgis v2.2 Oracle custom queries are supported."""
        return Qgis.QGIS_VERSION_INT >= 20200

    def hasTableColumnEditingSupport(self):
        """Tables can always be edited."""
        return True

    def hasCreateSpatialViewSupport(self):
        """We can create Spatial Views."""
        return True

    def fieldTypes(self):
        """From
        http://docs.oracle.com/cd/B28359_01/server.111/b28318/datatype.htm#CNCPT1828
        """
        return [
            "number", "number(9)",  # integers
            "number(9,2)", "number(*,4)", "binary_float",
            "binary_double",  # floats
            "varchar2(255)", "char(20)", "nvarchar2(255)",
            "nchar(20)",  # strings
            "date", "timestamp"  # date/time
        ]

    def getSchemaPrivileges(self, schema):
        """
        Schema privileges:
        (can create new objects, can access objects in schema)
        """
        # TODO: find the best way in Oracle do determine schema privileges
        schema = self.user if not schema else schema

        # In Oracle world, rights seems quite simple: only schema_owner can
        # create table in the schema
        if schema == self.user:
            return (True, True)
        # getSchemas request only extract schemas where user has access
        return (False, True)

    def getRawTablePrivileges(self, table, owner, grantee):
        """
        Retrieve privileges on a table in a schema for a specific
        user.
        """
        result = [False, False, False, False]
        # Inspect in all tab privs
        sql = u"""
        SELECT DISTINCT PRIVILEGE
        FROM ALL_TAB_PRIVS_RECD
        WHERE PRIVILEGE IN ('SELECT','INSERT','UPDATE','DELETE')
          AND TABLE_NAME = {0}
          AND OWNER = {1}
          AND GRANTEE IN ({2}, {3})
        """.format(self.quoteString(table),
                   self.quoteString(owner),
                   self.quoteString(grantee),
                   self.quoteString(grantee.upper()))

        c = self._execute(None, sql)
        res = self._fetchall(c)
        c.close()

        # Find which privilege is returned
        for line in res:
            if line[0] == u"SELECT":
                result[0] = True
            if line[0] == u"INSERT":
                result[1] = True
            if line[0] == u"UPDATE":
                result[2] = True
            if line[0] == u"DELETE":
                result[3] = True

        return result

    def getTablePrivileges(self, table):
        """Retrieve table privileges: (select, insert, update, delete)."""

        schema, tablename = self.getSchemaTableName(table)
        if self.user == schema:
            return [True, True, True, True]
        return self.getRawTablePrivileges(tablename, schema, self.user)

    def getSchemasCache(self):
        """Get the list of schemas from the cache."""
        sql = u"""
        SELECT DISTINCT ownername
        FROM "oracle_{}"
        ORDER BY ownername
        """.format(self.connName)
        c = self.cache_connection.cursor()
        c.execute(sql)
        res = c.fetchall()
        c.close()

        return res

    def getSchemas(self):
        """Get list of schemas in tuples:
        (oid, name, owner, perms, comment).
        """
        if self.userTablesOnly:
            return [(self.user,)]

        if self.hasCache():
            return self.getSchemasCache()

        # Use cache if avalaible:
        metatable = (u"all_objects WHERE object_type IN "
                     u"('TABLE','VIEW','SYNONYM')")
        if self.geometryColumnsOnly:
            metatable = u"all_sdo_geom_metadata"

        sql = u"""SELECT DISTINCT owner FROM {} ORDER BY owner""".format(
            metatable)

        c = self._execute(None, sql)
        res = self._fetchall(c)
        c.close()

        return res

    def getTables(self, schema=None, add_sys_tables=False):
        """Get list of tables."""
        if self.hasCache() and not self.populated:
            self.populated = True
            return self.getTablesCache(schema)

        tablenames = []
        items = []

        try:
            vectors = self.getVectorTables(schema)
            for tbl in vectors:
                tablenames.append((tbl[2], tbl[1]))
                items.append(tbl)
        except DbError:
            pass

        if self.allowGeometrylessTables:
            # get all non geographic tables and views
            prefix = u"ALL"
            owner = u"o.owner"
            where = u""
            if self.userTablesOnly:
                prefix = u"USER"
                owner = u"user As OWNER"
            if schema and not self.userTablesOnly:
                where = u"AND o.owner = {} ".format(
                    self.quoteString(schema))

            sql = u"""
            SELECT o.OBJECT_NAME, {0},
                   CASE o.OBJECT_TYPE
                   WHEN 'VIEW' THEN 1
                   ELSE 0 END As isView
            FROM {1}_OBJECTS o
            WHERE o.object_type IN ('TABLE','VIEW','SYNONYM')
            {2} {3}
            ORDER BY o.OBJECT_NAME
            """.format(owner, prefix, where,
                       u"" if add_sys_tables
                       else u"AND o.OBJECT_NAME NOT LIKE 'MDRT_%'")

            c = self._execute(None, sql)
            for tbl in self._fetchall(c):
                if tablenames.count((tbl[1], tbl[0])) <= 0:
                    item = list(tbl)
                    item.insert(0, Table.TableType)
                    items.append(item)

            c.close()

        self.populated = True

        listTables = sorted(items, key=cmp_to_key(lambda x, y: (x[1] > y[1]) - (x[1] < y[1])))

        if self.hasCache():
            self.updateCache(listTables, schema)
            return self.getTablesCache(schema)

        return listTables

    def getTablesCache(self, schema=None):
        """Get list of tables from SQLite cache."""

        tablenames = []
        items = []

        try:
            vectors = self.getVectorTablesCache(schema)
            for tbl in vectors:
                tablenames.append((tbl[2], tbl[1]))
                items.append(tbl)
        except DbError:
            pass

        if not self.allowGeometrylessTables:
            return sorted(items, key=cmp_to_key(lambda x, y: (x[1] > y[1]) - (x[1] < y[1])))

        # get all non geographic tables and views
        schema_where = u""
        if self.userTablesOnly:
            schema_where = u"AND ownername = '{}'".format(
                self.user)
        if schema and not self.userTablesOnly:
            schema_where = u"AND ownername = '{}'".format(
                schema)

        sql = u"""
        SELECT tablename, ownername, isview
        FROM "oracle_{0}"
        WHERE geometrycolname IS '' {1}
        ORDER BY tablename
        """.format(self.connName, schema_where)

        c = self.cache_connection.cursor()
        c.execute(sql)
        for tbl in c.fetchall():
            if tablenames.count((tbl[1], tbl[0])) <= 0:
                item = list(tbl)
                item.insert(0, Table.TableType)
                items.append(item)
        c.close()

        return sorted(items, key=cmp_to_key(lambda x, y: (x[1] > y[1]) - (x[1] < y[1])))

    def updateCache(self, tableList, schema=None):
        """Update the SQLite cache of table list for a schema."""

        data = []
        # First, we treat the list
        for table in tableList:
            line = ()
            # if the table is a view bring pkCols
            pkCols = None
            if int(table[3]) == 1:
                pkCols = self.pkCols((schema, table[1]))
            # Deals with non-geographic tables
            if table[0] == Table.TableType:
                line = (table[1], table[2], int(table[3]),
                        u"",
                        u",".join(pkCols) if pkCols else u"",
                        100, 0, u"")
            # Deals with vector tables
            elif table[0] == Table.VectorType:
                line = (table[1], table[2], int(table[3]),
                        table[4],
                        u",".join(pkCols) if pkCols else u"",
                        table[9],
                        table[8] if table[10] == u"-1" else table[10],
                        u"")
            else:
                continue
            data.append(line)

        # Then, empty the cache list
        sql = u"""
        DELETE FROM "oracle_{0}" {1}
        """.format(self.connName,
                   u"WHERE ownername = '{}'".format(schema) if schema else u"")
        self.cache_connection.execute(sql)
        self.cache_connection.commit()

        # Then we insert into SQLite database
        sql = u"""
        INSERT INTO "oracle_{}"(tablename, ownername, isview,
        geometrycolname, pkcols, geomtypes, geomsrids, sql)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?)
        """.format(self.connName)
        c = self.cache_connection.cursor()
        c.executemany(sql, data)
        c.close()
        self.cache_connection.commit()

    def singleGeomTypes(self, geomtypes, srids):
        """Intelligent wkbtype grouping (multi with non multi)"""
        if (QgsWkbTypes.Polygon in geomtypes
                and QgsWkbTypes.MultiPolygon in geomtypes):
            srids.pop(geomtypes.index(QgsWkbTypes.Polygon))
            geomtypes.pop(geomtypes.index(QgsWkbTypes.Polygon))
        if (QgsWkbTypes.Point in geomtypes
                and QgsWkbTypes.MultiPoint in geomtypes):
            srids.pop(geomtypes.index(QgsWkbTypes.Point))
            geomtypes.pop(geomtypes.index(QgsWkbTypes.Point))
        if (QgsWkbTypes.LineString in geomtypes
                and QgsWkbTypes.MultiLineString in geomtypes):
            srids.pop(geomtypes.index(QgsWkbTypes.LineString))
            geomtypes.pop(geomtypes.index(QgsWkbTypes.LineString))
        if QgsWkbTypes.Unknown in geomtypes and len(geomtypes) > 1:
            srids.pop(geomtypes.index(QgsWkbTypes.Unknown))
            geomtypes.pop(geomtypes.index(QgsWkbTypes.Unknown))

        return geomtypes, srids

    def getVectorTablesCache(self, schema=None):
        """Get list of table with a geometry column from SQLite cache
        it returns:
        name (table name)
        namespace (schema)
        type = 'view' (is a view?)
        geometry_column
        geometry_types (as WKB type)
        srids
        """
        schema_where = u""
        if self.userTablesOnly:
            schema_where = u"AND ownername = '{}'".format(
                self.user)
        if schema and not self.userTablesOnly:
            schema_where = u"AND ownername = '{}'".format(
                schema)

        sql = u"""
        SELECT tablename, ownername, isview,
               geometrycolname,
               geomtypes, geomsrids
        FROM "oracle_{0}"
        WHERE geometrycolname IS NOT '' {1}
        ORDER BY tablename
        """.format(self.connName, schema_where)

        items = []

        c = self.cache_connection.cursor()
        c.execute(sql)
        lst_tables = c.fetchall()
        c.close()

        # Handle multiple geometries tables
        for i, tbl in enumerate(lst_tables):
            item = list(tbl)
            srids = item.pop()
            geomtypes = item.pop()
            item.insert(0, Table.VectorType)
            if len(geomtypes) > 0 and len(srids) > 0:
                geomtypes = [int(l) for l in str(geomtypes).split(u",")]
                srids = [int(l) for l in str(srids).split(u",")]
                geomtypes, srids = self.singleGeomTypes(geomtypes, srids)
                for j in range(len(geomtypes)):
                    buf = list(item)
                    geomtype = geomtypes[j]
                    srid = srids[j]
                    datatype = QgsWkbTypes.displayString(QgsWkbTypes.flatType(QgsWkbTypes.singleType(geomtype)))
                    geo = datatype.upper()
                    buf.append(geo)
                    buf.append(geomtype)
                    buf.append(QgsWkbTypes.coordDimensions(geomtype))  # Dimensions
                    buf.append(srid)
                    buf.append(None)  # To respect ORTableVector row
                    buf.append(None)  # To respect ORTableVector row
                    items.append(buf)

        return items

    def getVectorTables(self, schema=None):
        """Get list of table with a geometry column
        it returns a table of tuples:
            name (table name)
            namespace (schema/owner)
            isView (is a view?)
            geometry_column
            srid
        """
        if not self.has_spatial:
            return []

        # discovery of all geographic tables
        prefix = u"all"
        owner = u"c.owner"
        where = None

        if not self.geometryColumnsOnly:
            where = u"WHERE c.data_type = 'SDO_GEOMETRY'"
        if schema and not self.userTablesOnly:
            where = u"{0} c.owner = {1}".format(
                u"{} AND".format(where) if where else u"WHERE",
                self.quoteString(schema))

        if self.userTablesOnly:
            prefix = u"user"
            owner = u"user As owner"
            if self.geometryColumnsOnly:
                where = u""

        sql = u"""
        SELECT c.table_name, {0},
               CASE o.OBJECT_TYPE
               WHEN 'VIEW' THEN 1
               ELSE 0 END As isView,
               c.column_name,
               {1}
        FROM {2}_{3} c
        JOIN {2}_objects o ON c.table_name = o.object_name
             AND o.object_type IN ('TABLE','VIEW','SYNONYM') {4} {5}
        ORDER BY TABLE_NAME
        """.format(owner,
                   u"c.srid" if self.geometryColumnsOnly
                   else u"NULL as srid",
                   prefix,
                   u"sdo_geom_metadata" if self.geometryColumnsOnly
                   else u"tab_columns",
                   u"" if self.userTablesOnly
                   else u"AND c.owner = o.owner",
                   where)

        # For each table, get all of the details
        items = []

        c = self._execute(None, sql)
        lst_tables = self._fetchall(c)
        c.close()

        for i, tbl in enumerate(lst_tables):
            item = list(tbl)
            detectedSrid = item.pop()
            if detectedSrid == NULL:
                detectedSrid = u"-1"
            else:
                detectedSrid = int(detectedSrid)

            if schema:
                table_name = u"{0}.{1}".format(self.quoteId(schema),
                                               self.quoteId(item[0]))
            else:
                table_name = self.quoteId(item[0])
            geocol = self.quoteId(item[3])
            geomMultiTypes, multiSrids = self.getTableGeomTypes(
                table_name, geocol)
            geomtypes = list(geomMultiTypes)
            srids = list(multiSrids)
            item.insert(0, Table.VectorType)

            geomtypes, srids = self.singleGeomTypes(geomtypes, srids)

            for j in range(len(geomtypes)):
                buf = list(item)
                geomtype = geomtypes[j]
                datatype = QgsWkbTypes.displayString(QgsWkbTypes.flatType(QgsWkbTypes.singleType(geomtype)))
                geo = datatype.upper()
                buf.append(geo)  # Geometry type as String
                buf.append(geomtype)  # Qgis.WkbType
                buf.append(QgsWkbTypes.coordDimensions(geomtype))  # Dimensions
                buf.append(detectedSrid)  # srid
                if not self.onlyExistingTypes:
                    geomMultiTypes.append(0)
                    multiSrids.append(multiSrids[0])
                buf.append(u",".join([str(x) for x in
                                      geomMultiTypes]))
                buf.append(u",".join([str(x) for x in multiSrids]))
                items.append(buf)

            if self.allowGeometrylessTables and buf[-6] != u"UNKNOWN":
                copybuf = list(buf)
                copybuf[4] = u""
                copybuf[-6] = u"UNKNOWN"
                copybuf[-5] = QgsWkbTypes.NullGeometry
                copybuf[-2] = QgsWkbTypes.NullGeometry
                copybuf[-1] = u"0"
                items.append(copybuf)

        return items

    def getTableComment(self, table, objectType):
        """Return the general comment for the object"""

        schema, tablename = self.getSchemaTableName(table)
        data_prefix = u"ALL" if schema else u"USER"
        where = u"AND OWNER = {}".format(
            self.quoteString(schema)) if schema else u""
        if objectType in [u"TABLE", u"VIEW"]:
            data_table = u"{}_TAB_COMMENTS"
            table = u"TABLE"
        elif objectType == u"MATERIALIZED VIEW":
            data_table = u"{}_MVIEW_COMMENTS"
            table = u"MVIEW"
        else:
            return None

        data_table = data_table.format(data_prefix)
        sql = u"""
        SELECT COMMENTS FROM {0} WHERE {1}_NAME = {2}
        {3}
        """.format(data_table, table,
                   self.quoteString(tablename),
                   where)

        c = self._execute(None, sql)
        res = self._fetchone(c)
        c.close()

        if res:
            return res[0]

        return None

    def getTableType(self, table):
        """Return the type of a table between the following:
        * Table
        * View
        * Materialized view
        """

        schema, tablename = self.getSchemaTableName(table)
        sql = u"""
        SELECT OBJECT_TYPE FROM {0} WHERE OBJECT_NAME = {1} {2}
        """
        if schema:
            sql = sql.format(u"ALL_OBJECTS",
                             self.quoteString(tablename),
                             u"AND OWNER = {}".format(
                                 self.quoteString(schema)))
        else:
            sql = sql.format(u"USER_OBJECTS",
                             self.quoteString(tablename),
                             u"")

        c = self._execute(None, sql)
        res = self._fetchall(c)
        c.close()

        # Analyze return values
        if not res:
            return False
        else:
            types = [x[0] for x in res]
            if u"MATERIALIZED VIEW" in types:
                return u"MATERIALIZED VIEW"
            elif u"VIEW" in types:
                return u"VIEW"
            else:
                return u"TABLE"

    def pkCols(self, table):
        """Return the primary keys candidates for a view."""
        schema, tablename = self.getSchemaTableName(table)
        sql = u"""
        SELECT column_name
        FROM all_tab_columns
        WHERE owner={0}
        AND table_name={1}
        ORDER BY column_id
        """.format(self.quoteString(schema) if schema else self.user,
                   self.quoteString(tablename))
        c = self._execute(None, sql)
        res = self._fetchall(c)
        c.close()

        return [x[0] for x in res] if res else None

    def getTableGeomTypes(self, table, geomCol):
        """Return all the wkbTypes for a table by requesting geometry
        column.
        """

        estimated = u""
        if self.useEstimatedMetadata:
            estimated = u"AND ROWNUM < 100"

        # Grab all of geometry types from the layer
        query = u"""
        SELECT DISTINCT a.{0}.SDO_GTYPE As gtype,
                        a.{0}.SDO_SRID
        FROM {1} a
        WHERE a.{0} IS NOT NULL {2}
        ORDER BY a.{0}.SDO_GTYPE
        """.format(geomCol, table, estimated)

        try:
            c = self._execute(None, query)
        except DbError:  # handle error views or other problems
            return [QgsWkbTypes.Unknown], [-1]

        rows = self._fetchall(c)
        c.close()

        # Handle results
        if len(rows) == 0:
            return [QgsWkbTypes.Unknown], [-1]

        # A dict to store the geomtypes
        geomtypes = []
        srids = []
        for row in rows:
            if row[1] == NULL:
                srids.append(-1)
            else:
                srids.append(int(row[1]))
            if int(row[0]) in list(OracleDBConnector.ORGeomTypes.keys()):
                geomtypes.append(OracleDBConnector.ORGeomTypes[int(row[0])])
            else:
                geomtypes.append(QgsWkbTypes.Unknown)

        return geomtypes, srids

    def getTableMainGeomType(self, table, geomCol):
        """Return the best wkbType for a table by requesting geometry
        column.
        """

        geomTypes, srids = self.getTableGeomTypes(table, geomCol)

        # Make the decision:
        wkbType = QgsWkbTypes.Unknown
        srid = -1
        order = [QgsWkbTypes.MultiPolygon25D, QgsWkbTypes.Polygon25D,
                 QgsWkbTypes.MultiPolygon, QgsWkbTypes.Polygon,
                 QgsWkbTypes.MultiLineString25D, QgsWkbTypes.LineString25D,
                 QgsWkbTypes.MultiLineString, QgsWkbTypes.LineString,
                 QgsWkbTypes.MultiPoint25D, QgsWkbTypes.Point25D,
                 QgsWkbTypes.MultiPoint, QgsWkbTypes.Point]
        for geomType in order:
            if geomType in geomTypes:
                wkbType = geomType
                srid = srids[geomTypes.index(geomType)]
                break

        return wkbType, srid

    def getTableRowEstimation(self, table):
        """ Find the estimated number of rows of a table. """
        schema, tablename = self.getSchemaTableName(table)
        prefix = u"ALL" if schema else u"USER"
        where = u"AND OWNER = {}".format(
            self.quoteString(schema)) if schema else u""

        sql = u"""
        SELECT NUM_ROWS FROM {0}_ALL_TABLES
        WHERE TABLE_NAME = {1}
        {2}
        """.format(prefix, self.quoteString(tablename), where)

        c = self._execute(None, sql)
        res = self._fetchone(c)
        c.close()

        if not res or res[0] == NULL:
            return 0
        else:
            return int(res[0])

    def getTableDates(self, table):
        """ Returns the modification/creation dates of an object"""
        schema, tablename = self.getSchemaTableName(table)
        prefix = u"ALL" if schema else u"USER"
        where = u"AND OWNER = {}".format(
            self.quoteString(schema)) if schema else u""

        sql = u"""
        SELECT CREATED, LAST_DDL_TIME FROM {0}_OBJECTS
        WHERE OBJECT_NAME = {1}
        {2}
        """.format(prefix, self.quoteString(tablename), where)

        c = self._execute(None, sql)
        res = self._fetchone(c)
        c.close()

        if not res:
            return None, None

        return res[0], res[1]

    def getTableRowCount(self, table):
        """Returns the number of rows of the table."""
        c = self._execute(
            None, u"SELECT COUNT(*) FROM {}".format(self.quoteId(table)))
        res = self._fetchone(c)[0]
        c.close()

        return res

    def getTableFields(self, table):
        """Returns list of columns in table."""

        schema, tablename = self.getSchemaTableName(table)
        schema_where = u" AND a.OWNER={}".format(
            self.quoteString(schema) if schema else "")
        sql = u"""
        SELECT a.COLUMN_ID As ordinal_position,
               a.COLUMN_NAME As column_name,
               a.DATA_TYPE As data_type,
               CASE a.DATA_TYPE
                 WHEN 'NUMBER' THEN a.DATA_PRECISION
                 ELSE a.DATA_LENGTH END As char_max_len,
               a.DATA_SCALE As modifier,
               a.NULLABLE As nullable,
               a.DEFAULT_LENGTH As hasdefault,
               a.DATA_DEFAULT As default_value,
               a.DATA_TYPE As formatted_type,
               c.COMMENTS
        FROM ALL_TAB_COLUMNS a
            JOIN ALL_COL_COMMENTS c ON
                a.TABLE_NAME = c.TABLE_NAME
                AND a.COLUMN_NAME = c.COLUMN_NAME
                AND a.OWNER = c.OWNER
        WHERE a.TABLE_NAME = {0} {1}
        ORDER BY a.COLUMN_ID
        """.format(self.quoteString(tablename), schema_where)

        c = self._execute(None, sql)
        res = self._fetchall(c)
        c.close()
        return res

    def getSpatialFields(self, table):
        """Returns the list of geometric columns"""
        fields = self.getTableFields(table)
        geomFields = []
        for field in fields:
            if field[2] == u"SDO_GEOMETRY":
                geomFields.append(field[1])

        return geomFields

    def getTableIndexes(self, table):
        """Get info about table's indexes."""
        schema, tablename = self.getSchemaTableName(table)
        schema_where = u" AND i.OWNER = {} ".format(
            self.quoteString(schema) if schema else "")

        sql = u"""
        SELECT i.INDEX_NAME, c.COLUMN_NAME, i.ITYP_NAME,
               i.STATUS, i.LAST_ANALYZED, i.COMPRESSION,
               i.UNIQUENESS
        FROM ALL_INDEXES i
        INNER JOIN ALL_IND_COLUMNS c ON i.index_name = c.index_name
        WHERE i.table_name = {0} {1}
        """.format(self.quoteString(tablename), schema_where)

        c = self._execute(None, sql)
        res = self._fetchall(c)
        c.close()

        return res

    def getMViewInfo(self, table):
        """Find some information about materialized views"""
        schema, tablename = self.getSchemaTableName(table)
        where = u" AND a.OWNER = {} ".format(
            self.quoteString(schema)) if schema else u""
        prefix = u"ALL" if schema else u"USER"
        sql = u"""
        SELECT a.REFRESH_MODE,
               a.REFRESH_METHOD, a.BUILD_MODE, a.FAST_REFRESHABLE,
               a.LAST_REFRESH_TYPE, a.LAST_REFRESH_DATE, a.STALENESS,
               a.STALE_SINCE, a.COMPILE_STATE, a.USE_NO_INDEX
        FROM {0}_MVIEWS a
        WHERE MVIEW_NAME = {1}
        {2}
        """.format(prefix, self.quoteString(tablename), where)

        c = self._execute(None, sql)
        res = self._fetchone(c)
        c.close()

        return res

    def getTableConstraints(self, table):
        """Find all the constraints for a table."""
        schema, tablename = self.getSchemaTableName(table)
        schema_where = u" AND c.OWNER={} ".format(
            self.quoteString(schema)) if schema else u""

        sql = u"""
        SELECT a.CONSTRAINT_NAME, a.CONSTRAINT_TYPE,
               c.COLUMN_NAME, a.VALIDATED, a.GENERATED, a.STATUS,
               a.SEARCH_CONDITION, a.DELETE_RULE,
               CASE WHEN b.TABLE_NAME IS NULL THEN NULL
                    ELSE b.OWNER || '.' || b.TABLE_NAME END
               As F_TABLE, b.COLUMN_NAME As F_COLUMN
        FROM ALL_CONS_COLUMNS c
        INNER JOIN ALL_CONSTRAINTS a ON
                   a.CONSTRAINT_NAME = c.CONSTRAINT_NAME
        LEFT OUTER JOIN ALL_CONS_COLUMNS b ON
                        b.CONSTRAINT_NAME = a.R_CONSTRAINT_NAME
                        AND a.R_OWNER = b.OWNER
                        AND b.POSITION = c.POSITION
        WHERE c.TABLE_NAME = {0} {1}
        """.format(self.quoteString(tablename), schema_where)

        c = self._execute(None, sql)
        res = self._fetchall(c)
        c.close()

        return res

    def getTableTriggers(self, table):
        """Find all the triggers of the table."""
        schema, tablename = self.getSchemaTableName(table)

        sql = u"""
        SELECT TRIGGER_NAME, TRIGGERING_EVENT, TRIGGER_TYPE, STATUS
        FROM ALL_TRIGGERS
        WHERE TABLE_OWNER = {0}
        AND TABLE_NAME = {1}
        """.format(self.quoteString(schema), self.quoteString(tablename))

        c = self._execute(None, sql)
        res = self._fetchall(c)
        c.close()

        return res

    def enableAllTableTriggers(self, enable, table):
        """Enable or disable all triggers on table."""
        triggers = [l[0] for l in self.getTableTriggers(table)]
        for trigger in triggers:
            self.enableTableTrigger(trigger, enable, table)

    def enableTableTrigger(self, trigger, enable, table):
        """Enable or disable one trigger on table."""
        schema, tablename = self.getSchemaTableName(table)
        trigger = u".".join([self.quoteId(schema), self.quoteId(trigger)])
        sql = u"ALTER TRIGGER {0} {1}".format(trigger, "ENABLE" if
                                              enable else "DISABLE")
        self._execute_and_commit(sql)

    def deleteTableTrigger(self, trigger, table):
        """Delete the trigger on a table."""
        schema, tablename = self.getSchemaTableName(table)
        trigger = u".".join([self.quoteId(schema), self.quoteId(trigger)])
        sql = u"DROP TRIGGER {}".format(trigger)
        self._execute_and_commit(sql)

    def canUpdateMetadata(self, table):
        """Verify if user can update metadata table
        returns False or metadata table name.
        """
        schema, tablename = self.getSchemaTableName(table)
        metadata = False
        # User can only update in USER_SDO_GEOM_METADATA
        if self.getRawTablePrivileges('USER_SDO_GEOM_METADATA', 'MDSYS',
                                      'PUBLIC')[2]:
            tbQuery = u"""
            SELECT COUNT(*) FROM USER_SDO_GEOM_METADATA
            WHERE TABLE_NAME = {0}
            """.format(self.quoteString(tablename))
            c = self._execute(None, tbQuery)
            res = self._fetchone(c)
            c.close()

            if res:
                if res[0] > 0:
                    metadata = True

        return metadata

    def getTableExtent(self, table, geom):
        """Calculate the real table extent."""
        schema, tablename = self.getSchemaTableName(table)
        tableQuote = u"'{0}.{1}'".format(schema, tablename)
        # Extent calculation without spatial index
        extentFunction = u"""SDO_AGGR_MBR("{0}")""".format(geom)
        fromTable = u'"{0}"."{1}"'.format(schema, tablename)

        # if table as spatial index:
        indexes = self.getTableIndexes(table)
        if indexes:
            if u"SPATIAL_INDEX" in [f[2] for f in indexes]:
                extentFunction = u"SDO_TUNE.EXTENT_OF({0}, {1})".format(
                    tableQuote, self.quoteString(geom))
                fromTable = u"DUAL"

        sql = u"""
        SELECT
        SDO_GEOM.SDO_MIN_MBR_ORDINATE({0}, 1),
        SDO_GEOM.SDO_MIN_MBR_ORDINATE({0}, 2),
        SDO_GEOM.SDO_MAX_MBR_ORDINATE({0}, 1),
        SDO_GEOM.SDO_MAX_MBR_ORDINATE({0}, 2)
        FROM {1}
        """.format(extentFunction, fromTable)

        try:
            c = self._execute(None, sql)
        except DbError:  # no spatial index on table, try aggregation
            return None

        res = self._fetchone(c)
        c.close()

        if not res:
            res = None

        return res if res else None

    def getTableEstimatedExtent(self, table, geom):
        """Find out estimated extent (from metadata view)."""
        res = []
        schema, tablename = self.getSchemaTableName(table)
        where = u"""
        WHERE TABLE_NAME = {}
        AND COLUMN_NAME = {}
        """.format(self.quoteString(tablename),
                   self.quoteString(geom))
        if schema:
            where = u"{} AND OWNER = {}".format(
                where, self.quoteString(schema))

        request = u"""
        SELECT SDO_LB, SDO_UB
        FROM ALL_SDO_GEOM_METADATA m,
             TABLE(m.DIMINFO)
        {0}
        AND SDO_DIMNAME = '{1}'
        """
        for dimension in [u"X", u"Y"]:
            sql = request.format(where, dimension)
            try:
                c = self._execute(None, sql)
            except DbError:  # no statistics for the current table
                return None

            res_d = self._fetchone(c)
            c.close()

            if not res_d or len(res_d) < 2:
                return None
            elif res_d[0] == NULL:
                return None
            else:
                res.extend(res_d)

        return [res[0], res[2], res[1], res[3]]

    def getDefinition(self, view, objectType):
        """Returns definition of the view."""

        schema, tablename = self.getSchemaTableName(view)
        where = u""
        if schema:
            where = u" AND OWNER={} ".format(
                self.quoteString(schema))

        # Query to grab a view definition
        if objectType == u"VIEW":
            sql = u"""
            SELECT TEXT FROM ALL_VIEWS WHERE VIEW_NAME = {0} {1}
            """.format(self.quoteString(tablename), where)
        elif objectType == u"MATERIALIZED VIEW":
            sql = u"""
            SELECT QUERY FROM ALL_MVIEWS WHERE MVIEW_NAME = {0} {1}
            """.format(self.quoteString(tablename), where)
        else:
            return None

        c = self._execute(None, sql)
        res = self._fetchone(c)
        c.close()

        return res[0] if res else None

    def getSpatialRefInfo(self, srid):
        """Returns human name from an srid as describe in Oracle sys
        table.
        """
        if not self.has_spatial:
            return

        try:
            c = self._execute(
                None,
                (u"SELECT CS_NAME FROM MDSYS.CS_SRS WHERE"
                 u" SRID = {}".format(srid)))
        except DbError:
            return
        sr = self._fetchone(c)
        c.close()

        return sr[0] if sr else None

    def isVectorTable(self, table):
        """Determine if a table is a vector one by looking into
        metadata view.
        """
        if self.has_geometry_columns and self.has_geometry_columns_access:
            schema, tablename = self.getSchemaTableName(table)
            where = u"WHERE TABLE_NAME = {}".format(
                self.quoteString(tablename))
            if schema:
                where = u"{} AND OWNER = {}".format(where,
                                                    self.quoteString(schema))
            sql = u"""
            SELECT COUNT(*)
            FROM ALL_SDO_GEOM_METADATA
            {}
            """.format(where)

            c = self._execute(None, sql)
            res = self._fetchone(c)
            c.close()
            return res is not None and res[0] > 0

        return False

    def createTable(self, table, field_defs, pkey):
        """Create ordinary table
        'fields' is array containing field definitions
        'pkey' is the primary key name
        """
        if len(field_defs) == 0:
            return False

        sql = "CREATE TABLE {} (".format(self.quoteId(table))
        sql += u", ".join(field_defs)
        if pkey:
            sql += u", PRIMARY KEY ({})".format(self.quoteId(pkey))
        sql += ")"

        self._execute_and_commit(sql)
        return True

    def deleteTable(self, table):
        """Delete table and its reference in sdo_geom_metadata."""

        schema, tablename = self.getSchemaTableName(table)

        if self.isVectorTable(table):
            self.deleteMetadata(table)

        sql = u"DROP TABLE {}".format(self.quoteId(table))
        self._execute_and_commit(sql)

    def emptyTable(self, table):
        """Delete all the rows of a table."""

        sql = u"TRUNCATE TABLE {}".format(self.quoteId(table))
        self._execute_and_commit(sql)

    def renameTable(self, table, new_table):
        """Rename a table inside the database."""
        schema, tablename = self.getSchemaTableName(table)
        if new_table == tablename:
            return

        c = self._get_cursor()

        # update geometry_columns if Spatial is enabled
        if self.isVectorTable(table):
            self.updateMetadata(table, None, new_table=new_table)

        sql = u"RENAME {0} TO {1}".format(
            self.quoteId(tablename), self.quoteId(new_table))
        self._execute(c, sql)

        self._commit()

    def createView(self, view, query):
        """Create a view as defined."""
        sql = u"CREATE VIEW {0} AS {1}".format(self.quoteId(view),
                                               query)
        self._execute_and_commit(sql)

    def createSpatialView(self, view, query):
        """Creates a spatial view and update metadata table."""
        # What is the view name ?
        if len(view.split(u".")) > 1:
            schema, view = view.split(u".")
        else:
            schema = self.user
        view = (schema, view)

        # First create the view
        self.createView(view, query)

        # Grab the geometric column(s)
        fields = self.getSpatialFields(view)
        if not fields:
            return False

        for geoCol in fields:
            # Grab SRID
            geomTypes, srids = self.getTableGeomTypes(view, geoCol)

            # Calculate the extent
            extent = self.getTableExtent(view, geoCol)

            # Insert information into metadata table
            self.insertMetadata(view, geoCol, extent, srids[0])

        return True

    def deleteView(self, view):
        """Delete a view."""
        schema, tablename = self.getSchemaTableName(view)

        if self.isVectorTable(view):
            self.deleteMetadata(view)

        sql = u"DROP VIEW {}".format(self.quoteId(view))
        self._execute_and_commit(sql)

    def createSchema(self, schema):
        """Create a new empty schema in database."""
        # Not tested
        sql = u"CREATE SCHEMA AUTHORIZATION {}".format(
            self.quoteId(schema))
        self._execute_and_commit(sql)

    def deleteSchema(self, schema):
        """Drop (empty) schema from database."""
        sql = u"DROP USER {} CASCADE".format(self.quoteId(schema))
        self._execute_and_commit(sql)

    def renameSchema(self, schema, new_schema):
        """Rename a schema in the database."""
        # Unsupported in Oracle
        pass

    def addTableColumn(self, table, field_def):
        """Add a column to a table."""
        sql = u"ALTER TABLE {0} ADD {1}".format(self.quoteId(table),
                                                field_def)
        self._execute_and_commit(sql)

    def deleteTableColumn(self, table, column):
        """Delete column from a table."""
        # Delete all the constraints for this column
        constraints = [f[0] for f in self.getTableConstraints(table)
                       if f[2] == column]
        for constraint in constraints:
            self.deleteTableConstraint(table, constraint)

        # Delete all the indexes for this column
        indexes = [f[0] for f in self.getTableIndexes(table) if f[1] == column]
        for ind in indexes:
            self.deleteTableIndex(table, ind)

        # Delete metadata is we have a geo column
        if self.isGeometryColumn(table, column):
            self.deleteMetadata(table, column)

        sql = u"ALTER TABLE {0} DROP COLUMN {1}".format(
            self.quoteId(table), self.quoteId(column))
        self._execute_and_commit(sql)

    def updateTableColumn(self, table, column, new_name=None,
                          data_type=None, not_null=None,
                          default=None, comment=None):
        """Update properties of a column in a table."""

        schema, tablename = self.getSchemaTableName(table)

        c = self._get_cursor()

        # update column definition
        col_actions = []
        if data_type:
            col_actions.append(u"{}".format(data_type))
        if default:
            col_actions.append(u"DEFAULT {}".format(default))
        else:
            col_actions.append(u"DEFAULT NULL")

        if not_null:
            col_actions.append(u"NOT NULL")
        if not_null is None:
            col_actions.append(u"NULL")

        if col_actions:
            sql = u"ALTER TABLE {0} MODIFY ( {1} {2} )".format(
                self.quoteId(table), self.quoteId(column),
                u" ".join(col_actions))
            self._execute(c, sql)

        # rename the column
        if new_name and new_name != column:
            isGeo = self.isGeometryColumn(table, column)
            sql = u"ALTER TABLE {0} RENAME COLUMN {1} TO {2}".format(
                self.quoteId(table), self.quoteId(column),
                self.quoteId(new_name))
            self._execute(c, sql)

            # update geometry_columns if Spatial is enabled
            if isGeo:
                self.updateMetadata(table, column, new_name)

        self._commit()

    def renameTableColumn(self, table, column, new_name):
        """Rename column in a table."""
        return self.updateTableColumn(table, column, new_name)

    def setTableColumnType(self, table, column, data_type):
        """Change column type."""
        return self.updateTableColumn(table, column, None, data_type)

    def setTableColumnNull(self, table, column, is_null):
        """Change whether column can contain null values."""
        return self.updateTableColumn(table, column, None, None, not is_null)

    def setTableColumnDefault(self, table, column, default):
        """Change column's default value.
        If default=None or an empty string drop default value.
        """
        return self.updateTableColumn(table, column, None, None, None, default)

    def isGeometryColumn(self, table, column):
        """Find if a column is geometric."""
        schema, tablename = self.getSchemaTableName(table)
        prefix = u"ALL" if schema else u"USER"
        where = u"AND owner = {} ".format(
            self.quoteString(schema)) if schema else ""

        sql = u"""
        SELECT COUNT(*)
        FROM {0}_SDO_GEOM_METADATA
        WHERE TABLE_NAME = {1}
              AND COLUMN_NAME = {2} {3}
        """.format(prefix, self.quoteString(tablename),
                   self.quoteString(column.upper()), where)

        c = self._execute(None, sql)
        res = self._fetchone(c)[0] > 0

        c.close()
        return res

    def refreshMView(self, table):
        """Refresh an MVIEW"""
        schema, tablename = self.getSchemaTableName(table)
        mview = u"{}.{}".format(schema, tablename) if schema else tablename
        sql = u"""
        BEGIN
          DBMS_MVIEW.REFRESH({},'?');
        END;
        """.format(self.quoteString(mview))

        self._execute_and_commit(sql)

    def deleteMetadata(self, table, geom_column=None):
        """Delete the metadata entry for a table"""
        schema, tablename = self.getSchemaTableName(table)
        if not (self.getRawTablePrivileges('USER_SDO_GEOM_METADATA',
                                           'MDSYS',
                                           'PUBLIC')[3] and
                schema == self.user):
            return False

        where = u"WHERE TABLE_NAME = {}".format(self.quoteString(tablename))
        if geom_column:
            where = (u"{} AND COLUMN_NAME = "
                     u"{}".format(where,
                                  self.quoteString(geom_column)))
        sql = u"DELETE FROM USER_SDO_GEOM_METADATA {}".format(where)

        self._execute_and_commit(sql)

    def updateMetadata(self, table, geom_column, new_geom_column=None,
                       new_table=None, extent=None, srid=None):
        """update the metadata table with the new information"""

        schema, tablename = self.getSchemaTableName(table)
        if not (self.getRawTablePrivileges('USER_SDO_GEOM_METADATA',
                                           'MDSYS',
                                           'PUBLIC')[2] and
                schema == self.user):
            return False

        where = u"WHERE TABLE_NAME = {}".format(self.quoteString(tablename))
        if geom_column:
            # in Metadata view, geographic column is always in uppercase
            where = (u"{} AND COLUMN_NAME = "
                     u"{}".format(where,
                                  self.quoteString(geom_column.upper())))

        update = u"SET"
        if srid == 0:
            srid = -1

        if srid:
            update = u"{} SRID = {}".format(update, srid)
        if extent:
            if len(extent) == 4:
                if update != u"SET":
                    update = u"{},".format(update)
                update = u"""{4} DIMINFO = MDSYS.SDO_DIM_ARRAY(
                MDSYS.SDO_DIM_ELEMENT('X', {0:.9f}, {1:.9f}, 0.005),
                MDSYS.SDO_DIM_ELEMENT('Y', {2:.9f}, {3:.9f}, 0.005))
                """.format(extent[0], extent[2], extent[1],
                           extent[3], update)
        if new_geom_column:
            if update != u"SET":
                update = u"{},".format(update)
            # in Metadata view, geographic column is always in uppercase
            update = (u"{} COLUMN_NAME = "
                      u"{}".format(update,
                                   self.quoteString(new_geom_column.upper())))

        if new_table:
            if update != u"SET":
                update = u"{},".format(update)
            update = (u"{} TABLE_NAME = "
                      u"{}".format(update,
                                   self.quoteString(new_table)))

        sql = u"UPDATE USER_SDO_GEOM_METADATA {0} {1}".format(update, where)

        self._execute_and_commit(sql)

    def insertMetadata(self, table, geom_column, extent, srid, dim=2):
        """ Insert a line for the table in Oracle Metadata table."""
        schema, tablename = self.getSchemaTableName(table)
        if not (self.getRawTablePrivileges('USER_SDO_GEOM_METADATA',
                                           'MDSYS',
                                           'PUBLIC')[1] and
                schema == self.user):
            return False

        # in Metadata view, geographic column is always in uppercase
        geom_column = geom_column.upper()
        if srid == 0:
            srid = -1

        if len(extent) != 4:
            return False
        dims = [u'X', u'Y', u'Z', u'T']
        extentParts = []
        for i in range(dim):
            extentParts.append(
                u"""MDSYS.SDO_DIM_ELEMENT(
                '{0}', {1:.9f}, {2:.9f}, 0.005)""".format(dims[i],
                                                          extent[i],
                                                          extent[i + 1]))
        extentParts = u",".join(extentParts)
        sqlExtent = u"""MDSYS.SDO_DIM_ARRAY(
                {})
                """.format(extentParts)

        sql = u"""
        INSERT INTO USER_SDO_GEOM_METADATA (TABLE_NAME,
                                           COLUMN_NAME, DIMINFO,
                                           SRID)
        VALUES({0}, {1},
               {2},
               {3})
            """.format(self.quoteString(tablename),
                       self.quoteString(geom_column),
                       sqlExtent, str(srid))

        self._execute_and_commit(sql)

    def addGeometryColumn(self, table, geom_column='GEOM',
                          geom_type=None, srid=-1, dim=2):
        """Add a geometry column and update Oracle Spatial
        metadata.
        """

        schema, tablename = self.getSchemaTableName(table)
        # in Metadata view, geographic column is always in uppercase
        geom_column = geom_column.upper()

        # Add the column to the table
        sql = u"ALTER TABLE {0} ADD {1} SDO_GEOMETRY".format(
            self.quoteId(table), self.quoteId(geom_column))

        self._execute_and_commit(sql)

        # Then insert the metadata
        extent = []
        for i in range(dim):
            extent.extend([-100000, 10000])
        self.insertMetadata(table, geom_column,
                            [-100000, 100000, -10000, 10000],
                            srid, dim)

    def deleteGeometryColumn(self, table, geom_column):
        """Delete a geometric column."""
        return self.deleteTableColumn(table, geom_column)

    def addTableUniqueConstraint(self, table, column):
        """Add a unique constraint to a table."""
        sql = u"ALTER TABLE {0} ADD UNIQUE ({1})".format(
            self.quoteId(table), self.quoteId(column))
        self._execute_and_commit(sql)

    def deleteTableConstraint(self, table, constraint):
        """Delete constraint in a table."""
        sql = u"ALTER TABLE {0} DROP CONSTRAINT {1}".format(
            self.quoteId(table), self.quoteId(constraint))
        self._execute_and_commit(sql)

    def addTablePrimaryKey(self, table, column):
        """Add a primary key (with one column) to a table."""
        sql = u"ALTER TABLE {0} ADD PRIMARY KEY ({1})".format(
            self.quoteId(table), self.quoteId(column))
        self._execute_and_commit(sql)

    def createTableIndex(self, table, name, column):
        """Create index on one column using default options."""
        sql = u"CREATE INDEX {0} ON {1} ({2})".format(
            self.quoteId(name), self.quoteId(table),
            self.quoteId(column))
        self._execute_and_commit(sql)

    def rebuildTableIndex(self, table, name):
        """Rebuild a table index"""
        schema, tablename = self.getSchemaTableName(table)
        sql = u"ALTER INDEX {} REBUILD".format(self.quoteId((schema, name)))
        self._execute_and_commit(sql)

    def deleteTableIndex(self, table, name):
        """Delete an index on a table."""
        schema, tablename = self.getSchemaTableName(table)
        sql = u"DROP INDEX {}".format(self.quoteId((schema, name)))
        self._execute_and_commit(sql)

    def createSpatialIndex(self, table, geom_column='GEOM'):
        """Create a spatial index on a geometric column."""
        geom_column = geom_column.upper()
        schema, tablename = self.getSchemaTableName(table)
        idx_name = self.quoteId(u"sidx_{0}_{1}".format(tablename,
                                                       geom_column))
        sql = u"""
        CREATE INDEX {0}
        ON {1}({2})
        INDEXTYPE IS MDSYS.SPATIAL_INDEX
        """.format(idx_name, self.quoteId(table),
                   self.quoteId(geom_column))
        self._execute_and_commit(sql)

    def deleteSpatialIndex(self, table, geom_column='GEOM'):
        """Delete a spatial index of a geometric column."""
        schema, tablename = self.getSchemaTableName(table)
        idx_name = self.quoteId(u"sidx_{0}_{1}".format(tablename,
                                                       geom_column))
        return self.deleteTableIndex(table, idx_name)

    def execution_error_types(self):
        return QtSqlDB.ExecError

    def connection_error_types(self):
        return QtSqlDB.ConnectionError

    def error_types(self):
        return self.connection_error_types(), self.execution_error_types()

    def _close_cursor(self, c):
        """new implementation of _close_cursor (because c.closed is
        psycopg2 specific and not DB API 2.0
        """
        try:
            if c:
                c.close()

        except self.error_types():
            pass

        return

    # moved into the parent class: DbConnector._execute()
    # def _execute(self, cursor, sql):
    #     pass

    # moved into the parent class: DbConnector._execute_and_commit()
    # def _execute_and_commit(self, sql):
    #     pass

    # moved into the parent class: DbConnector._get_cursor()
    # def _get_cursor(self, name=None):
    #     pass

    # moved into the parent class: DbConnector._fetchall()
    # def _fetchall(self, c):
    #     pass

    # moved into the parent class: DbConnector._fetchone()
    # def _fetchone(self, c):
    #     pass

    # moved into the parent class: DbConnector._commit()
    # def _commit(self):
    #     pass

    # moved into the parent class: DbConnector._rollback()
    # def _rollback(self):
    #     pass

    # moved into the parent class: DbConnector._get_cursor_columns()
    # def _get_cursor_columns(self, c):
    #     pass

    def getSqlDictionary(self):
        """Returns the dictionary for SQL dialog."""
        from .sql_dictionary import getSqlDictionary
        sql_dict = getSqlDictionary()

        # get schemas, tables and field names
        items = []

        # First look into the cache if available
        if self.hasCache():
            sql = u"""
            SELECT DISTINCT tablename FROM "oracle_{0}"
            UNION
            SELECT DISTINCT ownername FROM "oracle_{0}"
            """.format(self.connName)
            if self.userTablesOnly:
                sql = u"""
                SELECT DISTINCT tablename
                FROM "oracle_{conn}" WHERE ownername = '{user}'
                UNION
                SELECT DISTINCT ownername
                FROM "oracle_{conn}" WHERE ownername = '{user}'
                """.format(conn=self.connName, user=self.user)

            c = self.cache_connection.cursor()
            c.execute(sql)
            for row in c.fetchall():
                items.append(row[0])
            c.close()

        if self.hasCache():
            sql = u"""
            SELECT DISTINCT COLUMN_NAME FROM {}_TAB_COLUMNS
            """.format(u"USER" if self.userTablesOnly else
                       u"ALL")
        elif self.userTablesOnly:
            sql = u"""
            SELECT DISTINCT TABLE_NAME FROM USER_ALL_TABLES
            UNION
            SELECT USER FROM DUAL
            UNION
            SELECT DISTINCT COLUMN_NAME FROM USER_TAB_COLUMNS
            """
        else:
            sql = u"""
            SELECT TABLE_NAME FROM ALL_ALL_TABLES
            UNION
            SELECT DISTINCT OWNER FROM ALL_ALL_TABLES
            UNION
            SELECT DISTINCT COLUMN_NAME FROM ALL_TAB_COLUMNS
            """

        c = self._execute(None, sql)
        for row in self._fetchall(c):
            items.append(row[0])
        c.close()

        sql_dict["identifier"] = items
        return sql_dict

    def getQueryBuilderDictionary(self):
        from .sql_dictionary import getQueryBuilderDictionary
        return getQueryBuilderDictionary()

    def cancel(self):
        # how to cancel an Oracle query?
        pass
