Migrate roles, users, and permissions from Oracle to PolarDB for PostgreSQL (Compatible with Oracle)

Updated at:
Copy as MD

When you migrate your workloads from an Oracle database to PolarDB for PostgreSQL (Compatible with Oracle), you must fully replicate the original security model. This topic describes two migration methods. We recommend that you use the user and permission migration tool (polardb-schema-migration) to automate the migration. If your runtime environment cannot connect to both Oracle and PolarDB for PostgreSQL (Compatible with Oracle) at the same time, you can use SQL scripts to migrate manually.

Method selection

This topic provides the following two migration methods. Choose the one that fits your runtime environment.

Item

Runtime requirements

Efficiency

Use case

Method 1: (Recommended) Use the migration tool

Requires a host that can connect to both Oracle and PolarDB for PostgreSQL (Compatible with Oracle) (for example, an ECS instance) with Java 1.8 installed.

High. Run a single command to automatically complete all migration steps.

Most scenarios. Recommended as the primary option.

Method 2: Use SQL scripts

No network connectivity required. Export SQL on the Oracle side by using SQL*Plus, then transfer the SQL files to a host that can access PolarDB for PostgreSQL (Compatible with Oracle) and import them by using psql.

Medium. You must execute several SQL scripts manually, which makes the workflow tedious.

You cannot provide a host that connects to both Oracle and PolarDB for PostgreSQL (Compatible with Oracle) because of network isolation or similar reasons.

Method 1: (Recommended) Use the migration tool

Considerations

  • Oracle source requirements: The Oracle account used for the connection must have at least the CONNECT, CREATE SESSION, and SELECT_CATALOG_ROLE privileges. Use the following statement to grant them:

    GRANT CONNECT, CREATE SESSION, SELECT_CATALOG_ROLE TO myuser;
  • PolarDB target requirements:

    • You must first use Data Transmission Service (DTS) to complete the schema migration from Oracle to PolarDB.

    • The account that connects to PolarDB must be a privileged account.

  • Tool runtime requirements:

    • The migration tool must be deployed on a host that can connect to both Oracle and PolarDB, such as an ECS instance.

    • The runtime environment must have Java 1.8 installed.

Deploy the tool

  1. Download the migration tool JAR file polardb-schema-migration.jar and upload it to a host that can connect to both Oracle and PolarDB.

  2. Run the following command to verify the tool version:

    java -jar polardb-schema-migration.jar --version
  3. Create a config.json file with the following content. Adjust the database connection strings and log directory based on your environment. The migration process uses this configuration file.

    {
      "source": {
        "url": "jdbc:oracle:thin:@//<OracleHostIP>:<port>/<serviceName>",
        "username": "<OracleUser>",
        "password": "<OraclePassword>"
      },
      "destination": {
        "url": "jdbc:polardb://<PolarDBEndpoint>:<port>/<dbName>",
        "username": "<PolarDBUser>",
        "password": "<PolarDBPassword>"
      },
      "log": {
        "dir": "/path/to/log"
      }
    }

Migration steps

Create roles

Run the following command to export role (ROLE) definitions from Oracle and create them in PolarDB.

# Adjust the paths of the JAR file and config file based on your environment. The same applies to the commands that follow.
java -jar polardb-schema-migration.jar --config=/path/to/config.json --create-roles

Create users

Run the following command to export user (USER) definitions from Oracle and create them in PolarDB.

java -jar polardb-schema-migration.jar --config=/path/to/config.json --create-users

Because passwords are encrypted in the source database, password migration is not supported. After migration to PolarDB, all users have no password. You must set a password manually for each user:

ALTER USER myuser PASSWORD '<new_password>';

Grant object privileges

Run the following command to export the object privilege (OBJECT PRIVILEGE) grant records from Oracle and apply them in PolarDB. This grants privileges such as table SELECT/INSERT/UPDATE/DELETE and function EXECUTE to roles and users.

java -jar polardb-schema-migration.jar --config=/path/to/config.json --grant-object-privileges

Grant roles

Run the following command to export the role grant records from Oracle and apply them in PolarDB. When a role is granted to a user, the user obtains the role's privileges.

java -jar polardb-schema-migration.jar --config=/path/to/config.json --grant-roles

Grant SCHEMA privileges

Run the following command to grant the USAGE privilege on schemas to regular users to align with Oracle behavior. Oracle does not check this privilege, which is equivalent to granting it by default.

java -jar polardb-schema-migration.jar --config=/path/to/config.json --grant-schema-usage

Modify object owners

In Oracle, SCHEMA and USER/OWNER are the same concept. In PolarDB, they are independent. Run the following command to change the OWNER of each object to the user with the same name as the schema, achieving consistent SCHEMA-OWNER mapping in PolarDB.

java -jar polardb-schema-migration.jar --config=/path/to/config.json --change-object-owner

(Optional) Grant system privileges

System privileges are an advanced Oracle feature that primarily control DDL execution. In PolarDB, we recommend that you use a privileged account for DDL operations. Therefore, you generally do not need to migrate system privileges.

Important

Before you run this command, log on to the PolarDB console and enable the polar_enable_system_privilege parameter on the cluster Parameters page.

java -jar polardb-schema-migration.jar --config=/path/to/config.json --grant-system-privileges

(Optional) Set default roles

Default roles are an advanced Oracle feature that control which role privileges a user has by default after logon. In PolarDB, users automatically load privileges of all their granted roles at logon, which matches Oracle's default behavior. Therefore, you generally do not need to perform this migration step.

Important

Before you run this command, log on to the PolarDB console and enable the polar_enable_default_role parameter on the cluster Parameters page.

java -jar polardb-schema-migration.jar --config=/path/to/config.json --set-default-roles

(Optional) Create PROFILEs

PROFILEs are an advanced Oracle feature for password complexity checks, expiration policies, and similar controls. In PolarDB, users and passwords are typically managed from the console. Therefore, you generally do not need to migrate PROFILEs.

java -jar polardb-schema-migration.jar --config=/path/to/config.json --create-profiles

Method 2: Use SQL scripts

In this method, you first run PL/SQL programs by using SQL*Plus on the Oracle side to export roles, users, and permissions into SQL scripts. Then, you use a client such as psql to execute the SQL scripts in PolarDB. This method does not require network connectivity between Oracle and PolarDB.

Considerations

  • Oracle source requirements: Prepare a migration account in Oracle. The account must have at least the connection and system catalog query privileges. Use the following statements to grant them:

    CREATE USER <OracleUser> IDENTIFIED BY <OraclePassword>;
    GRANT CONNECT TO <OracleUser>;
    GRANT CREATE SESSION TO <OracleUser>;
    GRANT SELECT_CATALOG_ROLE TO <OracleUser>;
  • PolarDB target requirements: Prepare a privileged account in PolarDB. You can create it in the PolarDB console.

  • Password migration limit: Because passwords are encrypted in Oracle, this method does not migrate passwords. All users have no password after migration to PolarDB; you must set the password manually.

  • Oracle version notes: This topic provides separate scripts for Oracle 12c and later, and for Oracle 11g.

    • For Oracle 12c and later, you can use the ORACLE_MAINTAINED='N' column in system views to efficiently filter out Oracle system users and roles.

    • For Oracle 11g, the column is not available, so you must filter system objects by listing their common names explicitly. If some objects are not filtered in your environment, adjust the filter list dynamically.

Migration steps

Create roles

Run the following PL/SQL script in SQL*Plus to export the CREATE ROLE statements for all roles to the create_role.sql file (you can specify the output path in the SPOOL directive). Then, transfer the file to the PolarDB side and run it by using psql.

Oracle 12c and later

SET HEADING OFF
SET TRIMSPOOL ON
SET TRIMOUT ON
SET PAGESIZE 0
SET LINESIZE 2000
SET SERVEROUTPUT ON
-- The actual storage location for the output file. Adjust as needed.
SPOOL /my/path/to/create_role.sql

BEGIN
    FOR rec IN (
        SELECT 'CREATE ROLE ' || ROLE || ';' AS ddl_script
        FROM DBA_ROLES
        WHERE ORACLE_MAINTAINED = 'N'
        ORDER BY ROLE
    ) LOOP
        DBMS_OUTPUT.PUT_LINE(rec.ddl_script);
    END LOOP;
END;
/

SPOOL OFF
EXIT

Oracle 11g

SET HEADING OFF
SET TRIMSPOOL ON
SET TRIMOUT ON
SET PAGESIZE 0
SET LINESIZE 2000
SET SERVEROUTPUT ON
SPOOL /my/path/to/create_role.sql

BEGIN
    FOR rec IN (
        SELECT 'CREATE ROLE ' || ROLE || ';' AS ddl_script
        FROM DBA_ROLES
        WHERE ROLE NOT IN (
            'CONNECT', 'RESOURCE', 'DBA', 'SELECT_CATALOG_ROLE',
            'EXECUTE_CATALOG_ROLE', 'DELETE_CATALOG_ROLE', 'EXP_FULL_DATABASE',
            'IMP_FULL_DATABASE', 'LOGSTDBY_ADMINISTRATOR', 'RECOVERY_CATALOG_OWNER',
            'SCHEDULER_ADMIN', 'HS_ADMIN_SELECT_ROLE', 'HS_ADMIN_EXECUTE_ROLE',
            'HS_ADMIN_ROLE', 'GLOBAL_AQ_USER_ROLE', 'OEM_ADVISOR', 'OEM_MONITOR',
            'MGMT_USER', 'DATAPUMP_EXP_FULL_DATABASE', 'DATAPUMP_IMP_FULL_DATABASE',
            'ADM_PARALLEL_EXECUTE_TASK', 'GATHER_SYSTEM_STATISTICS',
            'OPTIMIZER_PROCESSING_RATE', 'ACCESS_CATALOG_ROLE', 'JAVAUSERPRIV',
            'JAVAIDPRIV', 'JAVASYSPRIV', 'JAVADEBUGPRIV', 'EJBCLIENT', 'JMXSERVER',
            'JAVA_ADMIN', 'JAVA_DEPLOY', 'AUTHENTICATEDUSER', 'XDB_SET_INVOKER',
            'XDB_WEBSERVICES', 'XDB_WEBSERVICES_WITH_PUBLIC', 'XDB_WEBSERVICES_OVER_HTTP',
            'XDBADMIN', 'XDB_WEBSERVICES_OVER_HTTPS', 'CTXAPP', 'ORDADMIN',
            'WM_ADMIN_ROLE', 'OLAP_USER', 'OLAP_DBA', 'OLAP_XS_ADMIN',
            'CSW_USR_ROLE', 'WFS_USR_ROLE', 'SPATIAL_CSW_ADMIN',
            'SPATIAL_WFS_ADMIN', 'LBAC_DBA', 'APEX_ADMINISTRATOR_ROLE',
            'FLOWS_FILES_ROLE', 'AQ_ADMINISTRATOR_ROLE', 'AQ_USER_ROLE',
            'CWM_USER', 'DBFS_ROLE', 'OLAPI_TRACE_USER', 'OWB$CLIENT',
            'OWB_DESIGNCENTER_VIEW', 'OWB_USER'
        )
        AND ROLE NOT LIKE 'APEX\_%' ESCAPE '\'
        AND ROLE NOT LIKE 'FLOWS\_%' ESCAPE '\'
        AND ROLE NOT LIKE 'ORA\_%' ESCAPE '\'
        AND ROLE NOT LIKE 'ORACLE\_%' ESCAPE '\'
        AND ROLE NOT LIKE 'SYS\_%' ESCAPE '\'
        ORDER BY ROLE
    ) LOOP
        DBMS_OUTPUT.PUT_LINE(rec.ddl_script);
    END LOOP;
END;
/

SPOOL OFF
EXIT

Create users

Run the following PL/SQL script in SQL*Plus to export the CREATE USER statements for all users. Then, transfer the SQL file to the PolarDB side and run it by using psql.

Oracle 12c and later

SET HEADING OFF
SET TRIMSPOOL ON
SET TRIMOUT ON
SET PAGESIZE 0
SET LINESIZE 2000
SET SERVEROUTPUT ON
SPOOL /my/path/to/create_user.sql

BEGIN
    FOR rec IN (
      SELECT 'CREATE USER ' || USERNAME || ';' AS ddl_script
      FROM DBA_USERS
      WHERE ORACLE_MAINTAINED = 'N'
      ORDER BY USERNAME
    ) LOOP
        DBMS_OUTPUT.PUT_LINE(rec.ddl_script);
    END LOOP;
END;
/

SPOOL OFF
EXIT

Oracle 11g

SET HEADING OFF
SET TRIMSPOOL ON
SET TRIMOUT ON
SET PAGESIZE 0
SET LINESIZE 2000
SET SERVEROUTPUT ON
SPOOL /my/path/to/create_user.sql

BEGIN
    FOR rec IN (
        SELECT 'CREATE USER ' || USERNAME || ';' AS ddl_script
        FROM DBA_USERS
        WHERE USERNAME NOT IN (
            'SYS', 'SYSTEM', 'DBSNMP', 'SYSMAN', 'OUTLN',
            'CTXSYS', 'MDSYS', 'ORDSYS', 'ORDDATA', 'ORDPLUGINS', 'SI_INFORMTN_SCHEMA',
            'WMSYS', 'EXFSYS', 'LBACSYS', 'XDB', 'ANONYMOUS', 'XS$NULL',
            'OLAPSYS', 'MDDATA', 'OWBSYS', 'OWBSYS_AUDIT',
            'APEX_030200', 'APEX_040000', 'APEX_040100', 'APEX_040200', 'APEX_050000',
            'APEX_PUBLIC_USER', 'FLOWS_FILES', 'FLOWS_030000', 'FLOWS_040000',
            'SPATIAL_CSW_ADMIN_USR', 'SPATIAL_WFS_ADMIN_USR',
            'DIP', 'ORACLE_OCM', 'APPQOSSYS', 'MGMT_VIEW', 'SCOTT', 'HR',
            'TSMSYS', 'WKPROXY', 'WK_TEST', 'PERFSTAT', 'TRACESVR',
            'GSMADMIN_INTERNAL', 'GSMCATUSER', 'GSMUSER', 'SYSBACKUP', 'SYSDG', 'SYSKM'
        )
        AND USERNAME NOT LIKE 'APEX\_%' ESCAPE '\'
        AND USERNAME NOT LIKE 'FLOWS\_%' ESCAPE '\'
        AND USERNAME NOT LIKE 'ORA\_%' ESCAPE '\'
        AND USERNAME NOT LIKE 'ORACLE\_%' ESCAPE '\'
        AND USERNAME NOT LIKE 'SYS\_%' ESCAPE '\'
        ORDER BY USERNAME
    ) LOOP
        DBMS_OUTPUT.PUT_LINE(rec.ddl_script);
    END LOOP;
END;
/

SPOOL OFF
EXIT

Because passwords are encrypted in Oracle, the exported CREATE USER statements do not include passwords. All users have no password after migration to PolarDB; you must set the password manually for each user:

ALTER USER <username> WITH PASSWORD '<new_password>';

Grant object privileges

Run the following PL/SQL script in SQL*Plus to export object privilege (OBJECT PRIVILEGE) GRANT statements to the grant_object.sql file. Then, transfer the file to the PolarDB side and run it by using psql. The script grants privileges such as table SELECT/INSERT/UPDATE/DELETE and function EXECUTE to roles and users.

The export script uses LISTAGG to merge multiple privileges on the same object for the same GRANTEE into a single GRANT statement (for example, SELECT, INSERT, and UPDATE are merged into GRANT SELECT, INSERT, UPDATE ON ...) and groups them by GRANTABLE (YES/NO) because WITH GRANT OPTION applies to the entire statement.

Note

When an object has many privileges, the LISTAGG result may exceed 4000 bytes and cause the ORA-01489 error. In this case, remove LISTAGG and emit one GRANT statement per privilege.

Oracle 12c and later

SET HEADING OFF
SET TRIMSPOOL ON
SET TRIMOUT ON
SET PAGESIZE 0
SET LINESIZE 2000
SET DEFINE OFF
SET SERVEROUTPUT ON
SPOOL /my/path/to/grant_object.sql

BEGIN
    FOR rec IN (
        SELECT
            'GRANT ' || LISTAGG(p.PRIVILEGE, ', ') WITHIN GROUP (ORDER BY p.PRIVILEGE) || ' ON ' ||
            CASE WHEN MIN(o.OBJECT_TYPE) IN ('PROCEDURE', 'FUNCTION', 'TYPE') THEN MIN(o.OBJECT_TYPE) || ' '
                 ELSE '' END ||
            '"' || p.OWNER || '"."' || p.TABLE_NAME || '" TO ' || p.GRANTEE ||
            CASE WHEN p.GRANTABLE = 'YES' THEN ' WITH GRANT OPTION' ELSE '' END ||
            ';' AS ddl_script
        FROM DBA_TAB_PRIVS p
        JOIN DBA_OBJECTS o ON p.TABLE_NAME = o.OBJECT_NAME AND o.OWNER = p.OWNER
        WHERE p.OWNER IN (SELECT USERNAME FROM DBA_USERS WHERE ORACLE_MAINTAINED = 'N')
          AND p.TABLE_NAME NOT LIKE 'ISEQ$$%'
          AND p.TABLE_NAME NOT LIKE 'AQ$%'
          AND o.OBJECT_TYPE NOT IN ('QUEUE', 'PACKAGE BODY', 'TYPE BODY')
          AND p.GRANTEE IN (
              SELECT USERNAME FROM DBA_USERS WHERE ORACLE_MAINTAINED = 'N'
              UNION
              SELECT ROLE FROM DBA_ROLES
              UNION
              SELECT 'PUBLIC' FROM DUAL
          )
        GROUP BY p.GRANTEE, p.OWNER, p.TABLE_NAME, p.GRANTABLE
        ORDER BY p.GRANTEE, p.OWNER, p.TABLE_NAME, p.GRANTABLE
    ) LOOP
        DBMS_OUTPUT.PUT_LINE(rec.ddl_script);
    END LOOP;
END;
/

SPOOL OFF
EXIT

Oracle 11g

SET HEADING OFF
SET TRIMSPOOL ON
SET TRIMOUT ON
SET PAGESIZE 0
SET LINESIZE 2000
SET DEFINE OFF
SET SERVEROUTPUT ON
SPOOL /my/path/to/grant_object.sql

BEGIN
    FOR rec IN (
        SELECT
            'GRANT ' || LISTAGG(p.PRIVILEGE, ', ') WITHIN GROUP (ORDER BY p.PRIVILEGE) || ' ON ' ||
            CASE WHEN MIN(o.OBJECT_TYPE) IN ('PROCEDURE', 'FUNCTION', 'TYPE') THEN MIN(o.OBJECT_TYPE) || ' '
                 ELSE '' END ||
            '"' || p.OWNER || '"."' || p.TABLE_NAME || '" TO ' || p.GRANTEE ||
            CASE WHEN p.GRANTABLE = 'YES' THEN ' WITH GRANT OPTION' ELSE '' END ||
            ';' AS ddl_script
        FROM DBA_TAB_PRIVS p
        JOIN DBA_OBJECTS o ON p.TABLE_NAME = o.OBJECT_NAME AND o.OWNER = p.OWNER
        WHERE p.OWNER IN (
            SELECT USERNAME FROM DBA_USERS WHERE USERNAME NOT IN (
                'SYS', 'SYSTEM', 'DBSNMP', 'SYSMAN', 'OUTLN',
                'CTXSYS', 'MDSYS', 'ORDSYS', 'ORDDATA', 'ORDPLUGINS', 'SI_INFORMTN_SCHEMA',
                'WMSYS', 'EXFSYS', 'LBACSYS', 'XDB', 'ANONYMOUS', 'XS$NULL',
                'OLAPSYS', 'MDDATA', 'OWBSYS', 'OWBSYS_AUDIT',
                'APEX_030200', 'APEX_040000', 'APEX_040100', 'APEX_040200', 'APEX_050000',
                'APEX_PUBLIC_USER', 'FLOWS_FILES', 'FLOWS_030000', 'FLOWS_040000',
                'SPATIAL_CSW_ADMIN_USR', 'SPATIAL_WFS_ADMIN_USR',
                'DIP', 'ORACLE_OCM', 'APPQOSSYS', 'MGMT_VIEW', 'SCOTT', 'HR',
                'TSMSYS', 'WKPROXY', 'WK_TEST', 'PERFSTAT', 'TRACESVR',
                'GSMADMIN_INTERNAL', 'GSMCATUSER', 'GSMUSER', 'SYSBACKUP', 'SYSDG', 'SYSKM'
            )
            AND USERNAME NOT LIKE 'APEX\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'FLOWS\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'ORA\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'ORACLE\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'SYS\_%' ESCAPE '\'
        )
          AND p.TABLE_NAME NOT LIKE 'ISEQ$$%'
          AND p.TABLE_NAME NOT LIKE 'AQ$%'
          AND o.OBJECT_TYPE NOT IN ('QUEUE', 'PACKAGE BODY', 'TYPE BODY')
          AND p.GRANTEE IN (
              SELECT USERNAME FROM DBA_USERS WHERE USERNAME NOT IN (
                  'SYS', 'SYSTEM', 'DBSNMP', 'SYSMAN', 'OUTLN',
                  'CTXSYS', 'MDSYS', 'ORDSYS', 'ORDDATA', 'ORDPLUGINS', 'SI_INFORMTN_SCHEMA',
                  'WMSYS', 'EXFSYS', 'LBACSYS', 'XDB', 'ANONYMOUS', 'XS$NULL',
                  'OLAPSYS', 'MDDATA', 'OWBSYS', 'OWBSYS_AUDIT',
                  'APEX_030200', 'APEX_040000', 'APEX_040100', 'APEX_040200', 'APEX_050000',
                  'APEX_PUBLIC_USER', 'FLOWS_FILES', 'FLOWS_030000', 'FLOWS_040000',
                  'SPATIAL_CSW_ADMIN_USR', 'SPATIAL_WFS_ADMIN_USR',
                  'DIP', 'ORACLE_OCM', 'APPQOSSYS', 'MGMT_VIEW', 'SCOTT', 'HR',
                  'TSMSYS', 'WKPROXY', 'WK_TEST', 'PERFSTAT', 'TRACESVR',
                  'GSMADMIN_INTERNAL', 'GSMCATUSER', 'GSMUSER', 'SYSBACKUP', 'SYSDG', 'SYSKM'
              )
              AND USERNAME NOT LIKE 'APEX\_%' ESCAPE '\'
              AND USERNAME NOT LIKE 'FLOWS\_%' ESCAPE '\'
              AND USERNAME NOT LIKE 'ORA\_%' ESCAPE '\'
              AND USERNAME NOT LIKE 'ORACLE\_%' ESCAPE '\'
              AND USERNAME NOT LIKE 'SYS\_%' ESCAPE '\'
              UNION
              SELECT ROLE FROM DBA_ROLES
              UNION
              SELECT 'PUBLIC' FROM DUAL
          )
        GROUP BY p.GRANTEE, p.OWNER, p.TABLE_NAME, p.GRANTABLE
        ORDER BY p.GRANTEE, p.OWNER, p.TABLE_NAME, p.GRANTABLE
    ) LOOP
        DBMS_OUTPUT.PUT_LINE(rec.ddl_script);
    END LOOP;
END;
/

SPOOL OFF
EXIT

Grant roles

Run the following PL/SQL script in SQL*Plus to export the role grant GRANT statements. Then, transfer the SQL file to the PolarDB side and run it by using psql. When a role is granted to a user, the user obtains the role's privileges.

Oracle 12c and later

SET HEADING OFF
SET TRIMSPOOL ON
SET TRIMOUT ON
SET PAGESIZE 0
SET LINESIZE 2000
SET SERVEROUTPUT ON
SPOOL /my/path/to/grant_role.sql

BEGIN
    FOR rec IN (
        WITH NON_ORACLE_USERS_AND_ROLES AS (
            SELECT USERNAME AS NAME FROM DBA_USERS WHERE ORACLE_MAINTAINED = 'N'
            UNION
            SELECT ROLE AS NAME FROM DBA_ROLES WHERE ORACLE_MAINTAINED = 'N'
        ),
        FILTERED_ROLE_PRIVS AS (
            SELECT GRANTEE, GRANTED_ROLE, ADMIN_OPTION
            FROM DBA_ROLE_PRIVS
            WHERE GRANTEE IN (SELECT NAME FROM NON_ORACLE_USERS_AND_ROLES)
        )
        SELECT
            CASE
                WHEN ADMIN_OPTION = 'YES' THEN
                    'GRANT ' || GRANTED_ROLE || ' TO ' || GRANTEE || ' WITH ADMIN OPTION;'
                ELSE
                    'GRANT ' || GRANTED_ROLE || ' TO ' || GRANTEE || ';'
            END AS ddl_script
        FROM FILTERED_ROLE_PRIVS
        ORDER BY GRANTEE, GRANTED_ROLE
    ) LOOP
        DBMS_OUTPUT.PUT_LINE(rec.ddl_script);
    END LOOP;
END;
/

SPOOL OFF
EXIT

Oracle 11g

SET HEADING OFF
SET TRIMSPOOL ON
SET TRIMOUT ON
SET PAGESIZE 0
SET LINESIZE 2000
SET SERVEROUTPUT ON
SPOOL /my/path/to/grant_role.sql

BEGIN
    FOR rec IN (
        WITH NON_ORACLE_USERS_AND_ROLES AS (
            SELECT USERNAME AS NAME FROM DBA_USERS WHERE USERNAME NOT IN (
                'SYS', 'SYSTEM', 'DBSNMP', 'SYSMAN', 'OUTLN',
                'CTXSYS', 'MDSYS', 'ORDSYS', 'ORDDATA', 'ORDPLUGINS', 'SI_INFORMTN_SCHEMA',
                'WMSYS', 'EXFSYS', 'LBACSYS', 'XDB', 'ANONYMOUS', 'XS$NULL',
                'OLAPSYS', 'MDDATA', 'OWBSYS', 'OWBSYS_AUDIT',
                'APEX_030200', 'APEX_040000', 'APEX_040100', 'APEX_040200', 'APEX_050000',
                'APEX_PUBLIC_USER', 'FLOWS_FILES', 'FLOWS_030000', 'FLOWS_040000',
                'SPATIAL_CSW_ADMIN_USR', 'SPATIAL_WFS_ADMIN_USR',
                'DIP', 'ORACLE_OCM', 'APPQOSSYS', 'MGMT_VIEW', 'SCOTT', 'HR',
                'TSMSYS', 'WKPROXY', 'WK_TEST', 'PERFSTAT', 'TRACESVR',
                'GSMADMIN_INTERNAL', 'GSMCATUSER', 'GSMUSER', 'SYSBACKUP', 'SYSDG', 'SYSKM'
            )
            AND USERNAME NOT LIKE 'APEX\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'FLOWS\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'ORA\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'ORACLE\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'SYS\_%' ESCAPE '\'
            UNION
            SELECT ROLE AS NAME FROM DBA_ROLES WHERE ROLE NOT IN (
                'CONNECT', 'RESOURCE', 'DBA', 'SELECT_CATALOG_ROLE',
                'EXECUTE_CATALOG_ROLE', 'DELETE_CATALOG_ROLE', 'EXP_FULL_DATABASE',
                'IMP_FULL_DATABASE', 'LOGSTDBY_ADMINISTRATOR', 'RECOVERY_CATALOG_OWNER',
                'SCHEDULER_ADMIN', 'HS_ADMIN_SELECT_ROLE', 'HS_ADMIN_EXECUTE_ROLE',
                'HS_ADMIN_ROLE', 'GLOBAL_AQ_USER_ROLE', 'OEM_ADVISOR', 'OEM_MONITOR',
                'MGMT_USER', 'DATAPUMP_EXP_FULL_DATABASE', 'DATAPUMP_IMP_FULL_DATABASE',
                'ADM_PARALLEL_EXECUTE_TASK', 'GATHER_SYSTEM_STATISTICS',
                'OPTIMIZER_PROCESSING_RATE', 'ACCESS_CATALOG_ROLE', 'JAVAUSERPRIV',
                'JAVAIDPRIV', 'JAVASYSPRIV', 'JAVADEBUGPRIV', 'EJBCLIENT', 'JMXSERVER',
                'JAVA_ADMIN', 'JAVA_DEPLOY', 'AUTHENTICATEDUSER', 'XDB_SET_INVOKER',
                'XDB_WEBSERVICES', 'XDB_WEBSERVICES_WITH_PUBLIC', 'XDB_WEBSERVICES_OVER_HTTP',
                'XDBADMIN', 'XDB_WEBSERVICES_OVER_HTTPS', 'CTXAPP', 'ORDADMIN',
                'WM_ADMIN_ROLE', 'OLAP_USER', 'OLAP_DBA', 'OLAP_XS_ADMIN',
                'CSW_USR_ROLE', 'WFS_USR_ROLE', 'SPATIAL_CSW_ADMIN',
                'SPATIAL_WFS_ADMIN', 'LBAC_DBA', 'APEX_ADMINISTRATOR_ROLE',
                'FLOWS_FILES_ROLE', 'AQ_ADMINISTRATOR_ROLE', 'AQ_USER_ROLE',
                'CWM_USER', 'DBFS_ROLE', 'OLAPI_TRACE_USER', 'OWB$CLIENT',
                'OWB_DESIGNCENTER_VIEW', 'OWB_USER'
            )
            AND ROLE NOT LIKE 'APEX\_%' ESCAPE '\'
            AND ROLE NOT LIKE 'FLOWS\_%' ESCAPE '\'
            AND ROLE NOT LIKE 'ORA\_%' ESCAPE '\'
            AND ROLE NOT LIKE 'ORACLE\_%' ESCAPE '\'
            AND ROLE NOT LIKE 'SYS\_%' ESCAPE '\'
        ),
        FILTERED_ROLE_PRIVS AS (
            SELECT GRANTEE, GRANTED_ROLE, ADMIN_OPTION
            FROM DBA_ROLE_PRIVS
            WHERE GRANTEE IN (SELECT NAME FROM NON_ORACLE_USERS_AND_ROLES)
        )
        SELECT
            CASE
                WHEN ADMIN_OPTION = 'YES' THEN
                    'GRANT ' || GRANTED_ROLE || ' TO ' || GRANTEE || ' WITH ADMIN OPTION;'
                ELSE
                    'GRANT ' || GRANTED_ROLE || ' TO ' || GRANTEE || ';'
            END AS ddl_script
        FROM FILTERED_ROLE_PRIVS
        ORDER BY GRANTEE, GRANTED_ROLE
    ) LOOP
        DBMS_OUTPUT.PUT_LINE(rec.ddl_script);
    END LOOP;
END;
/

SPOOL OFF
EXIT

Grant SCHEMA privileges

In Oracle, accessing an object only requires verifying that the current user has the object privilege. In PolarDB, the database first verifies that the user has the privilege on the schema (SCHEMA) that the object belongs to, then verifies the object privilege. To align with Oracle, run the following PL/pgSQL script in PolarDB to grant USAGE on all user schemas to all users:

DO $$
DECLARE
    schema_rec RECORD;
    sql_stmt TEXT;
BEGIN
    FOR schema_rec IN
        SELECT nspname FROM pg_namespace n
        WHERE nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast',
            'polar_catalog', 'sys', 'cron', 'wmsys', 'polar_feature_utils',
            'pg_bitmapindex', 'polar_dbms_stats', 'public')
          AND nspname NOT LIKE 'pg_temp_%' AND nspname NOT LIKE 'pg_toast_temp_%'
          AND nsppkgns = 0
        ORDER BY nspname
    LOOP
        sql_stmt := format('GRANT USAGE ON SCHEMA %I TO PUBLIC', schema_rec.nspname);
        RAISE NOTICE '%', sql_stmt;
        EXECUTE sql_stmt;
    END LOOP;
END $$;

Modify object owners

In Oracle, USER and SCHEMA are equivalent. Each user owns all objects in the schema with the same name by default. In PolarDB, users and schemas are independent. A user does not necessarily have privileges on the schema with the same name. To align with Oracle, run the following PL/pgSQL script (with a privileged account) in PolarDB by using psql to change the OWNER of each schema and all its objects to the user with the same name:

DO $$
DECLARE
    obj_rec RECORD;
    sql_stmt TEXT;
    processed INTEGER := 0;
BEGIN
    -- 1. Modify SCHEMA owners
    FOR obj_rec IN
        SELECT nspname FROM pg_namespace n
        WHERE nspname IN (SELECT rolname FROM pg_roles WHERE rolsuper = false)
          AND EXISTS (SELECT 1 FROM information_schema.schemata s WHERE s.schema_name = n.nspname)
          AND nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast',
              'polar_catalog', 'sys', 'cron', 'wmsys', 'polar_feature_utils',
              'pg_bitmapindex', 'polar_dbms_stats', 'public')
          AND nspname NOT LIKE 'pg_temp_%' AND nspname NOT LIKE 'pg_toast_temp_%'
          AND nsppkgns = 0
          AND (SELECT rolname FROM pg_roles WHERE oid = n.nspowner) != n.nspname
        ORDER BY nspname
    LOOP
        sql_stmt := format('ALTER SCHEMA %I OWNER TO %I', obj_rec.nspname, obj_rec.nspname);
        RAISE NOTICE '%', sql_stmt;
        EXECUTE sql_stmt;
        processed := processed + 1;
    END LOOP;

    -- 2. Modify TABLE owners
    FOR obj_rec IN
        SELECT schemaname, tablename FROM pg_tables t
        WHERE schemaname IN (SELECT rolname FROM pg_roles WHERE rolsuper = false)
          AND EXISTS (SELECT 1 FROM information_schema.schemata s WHERE s.schema_name = t.schemaname)
          AND schemaname NOT IN ('information_schema', 'pg_catalog', 'pg_toast',
              'polar_catalog', 'sys', 'cron', 'wmsys', 'polar_feature_utils',
              'pg_bitmapindex', 'polar_dbms_stats', 'public')
          AND t.tableowner != t.schemaname
        ORDER BY schemaname, tablename
    LOOP
        sql_stmt := format('ALTER TABLE %I.%I OWNER TO %I', obj_rec.schemaname, obj_rec.tablename, obj_rec.schemaname);
        RAISE NOTICE '%', sql_stmt;
        EXECUTE sql_stmt;
        processed := processed + 1;
    END LOOP;

    -- 2.1 Modify TABLE PARTITION owners
    -- ALTER TABLE on the parent table does not cascade to partitions; modify each partition individually.
    FOR obj_rec IN
        SELECT n.nspname AS schemaname, c.relname AS tablename
        FROM pg_class c
        JOIN pg_namespace n ON c.relnamespace = n.oid
        JOIN pg_inherits i ON c.oid = i.inhrelid
        WHERE n.nspname IN (SELECT rolname FROM pg_roles WHERE rolsuper = false)
          AND EXISTS (SELECT 1 FROM information_schema.schemata s WHERE s.schema_name = n.nspname)
          AND n.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast',
              'polar_catalog', 'sys', 'cron', 'wmsys', 'polar_feature_utils',
              'pg_bitmapindex', 'polar_dbms_stats', 'public')
          AND c.relkind = 'r'
          AND c.relispartition = true
          AND (SELECT rolname FROM pg_roles WHERE oid = c.relowner) != n.nspname
        ORDER BY n.nspname, c.relname
    LOOP
        sql_stmt := format('ALTER TABLE %I.%I OWNER TO %I', obj_rec.schemaname, obj_rec.tablename, obj_rec.schemaname);
        RAISE NOTICE '%', sql_stmt;
        EXECUTE sql_stmt;
        processed := processed + 1;
    END LOOP;

    -- 3. Modify VIEW owners
    FOR obj_rec IN
        SELECT schemaname, viewname FROM pg_views v
        WHERE schemaname IN (SELECT rolname FROM pg_roles WHERE rolsuper = false)
          AND EXISTS (SELECT 1 FROM information_schema.schemata s WHERE s.schema_name = v.schemaname)
          AND schemaname NOT IN ('information_schema', 'pg_catalog', 'pg_toast',
              'polar_catalog', 'sys', 'cron', 'wmsys', 'polar_feature_utils',
              'pg_bitmapindex', 'polar_dbms_stats', 'public')
          AND v.viewowner != v.schemaname
        ORDER BY schemaname, viewname
    LOOP
        sql_stmt := format('ALTER VIEW %I.%I OWNER TO %I', obj_rec.schemaname, obj_rec.viewname, obj_rec.schemaname);
        RAISE NOTICE '%', sql_stmt;
        EXECUTE sql_stmt;
        processed := processed + 1;
    END LOOP;

    -- 4. Modify MATERIALIZED VIEW owners
    FOR obj_rec IN
        SELECT schemaname, matviewname FROM pg_matviews mv
        WHERE schemaname IN (SELECT rolname FROM pg_roles WHERE rolsuper = false)
          AND EXISTS (SELECT 1 FROM information_schema.schemata s WHERE s.schema_name = mv.schemaname)
          AND schemaname NOT IN ('information_schema', 'pg_catalog', 'pg_toast',
              'polar_catalog', 'sys', 'cron', 'wmsys', 'polar_feature_utils',
              'pg_bitmapindex', 'polar_dbms_stats', 'public')
          AND mv.matviewowner != mv.schemaname
        ORDER BY schemaname, matviewname
    LOOP
        sql_stmt := format('ALTER MATERIALIZED VIEW %I.%I OWNER TO %I', obj_rec.schemaname, obj_rec.matviewname, obj_rec.schemaname);
        RAISE NOTICE '%', sql_stmt;
        EXECUTE sql_stmt;
        processed := processed + 1;
    END LOOP;

    -- 5. Modify SEQUENCE owners
    FOR obj_rec IN
        SELECT schemaname, sequencename FROM pg_sequences s
        WHERE schemaname IN (SELECT rolname FROM pg_roles WHERE rolsuper = false)
          AND EXISTS (SELECT 1 FROM information_schema.schemata s2 WHERE s2.schema_name = s.schemaname)
          AND schemaname NOT IN ('information_schema', 'pg_catalog', 'pg_toast',
              'polar_catalog', 'sys', 'cron', 'wmsys', 'polar_feature_utils',
              'pg_bitmapindex', 'polar_dbms_stats', 'public')
          AND s.sequenceowner != s.schemaname
        ORDER BY schemaname, sequencename
    LOOP
        sql_stmt := format('ALTER SEQUENCE %I.%I OWNER TO %I', obj_rec.schemaname, obj_rec.sequencename, obj_rec.schemaname);
        RAISE NOTICE '%', sql_stmt;
        EXECUTE sql_stmt;
        processed := processed + 1;
    END LOOP;

    -- 6. Modify FUNCTION owners (excluding functions inside extensions and packages)
    FOR obj_rec IN
        SELECT n.nspname AS schemaname, p.proname AS funcname,
            pg_get_function_identity_arguments(p.oid) AS args,
            CASE WHEN n.nspname = 'public' THEN 'PUBLIC' ELSE n.nspname END AS new_owner
        FROM pg_proc p JOIN pg_namespace n ON p.pronamespace = n.oid
        WHERE EXISTS (SELECT 1 FROM information_schema.schemata s WHERE s.schema_name = n.nspname)
          AND n.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast',
              'polar_catalog', 'sys', 'cron', 'wmsys', 'polar_feature_utils',
              'pg_bitmapindex', 'polar_dbms_stats')
          AND n.nspname NOT LIKE 'pg_temp_%' AND n.nspname NOT LIKE 'pg_toast_temp_%'
          AND nsppkgns = 0 AND p.prokind = 'f'
          AND NOT EXISTS (SELECT 1 FROM pg_depend d WHERE d.objid = p.oid AND d.deptype = 'e')
          AND (SELECT rolname FROM pg_roles WHERE oid = p.proowner) !=
              CASE WHEN n.nspname = 'public' THEN 'PUBLIC' ELSE n.nspname END
        ORDER BY n.nspname, p.proname
    LOOP
        sql_stmt := format('ALTER FUNCTION %I.%I(%s) OWNER TO %I',
            obj_rec.schemaname, obj_rec.funcname, obj_rec.args, obj_rec.new_owner);
        RAISE NOTICE '%', sql_stmt;
        EXECUTE sql_stmt;
        processed := processed + 1;
    END LOOP;

    -- 7. Modify PROCEDURE owners (excluding procedures inside packages)
    FOR obj_rec IN
        SELECT n.nspname AS schemaname, p.proname AS procname,
            pg_get_function_identity_arguments(p.oid) AS args
        FROM pg_proc p JOIN pg_namespace n ON p.pronamespace = n.oid
        WHERE n.nspname IN (SELECT rolname FROM pg_roles WHERE rolsuper = false)
          AND EXISTS (SELECT 1 FROM information_schema.schemata s WHERE s.schema_name = n.nspname)
          AND n.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast',
              'polar_catalog', 'sys', 'cron', 'wmsys', 'polar_feature_utils',
              'pg_bitmapindex', 'polar_dbms_stats', 'public')
          AND n.nspname NOT LIKE 'pg_temp_%' AND n.nspname NOT LIKE 'pg_toast_temp_%'
          AND nsppkgns = 0 AND p.prokind = 'p'
          AND (SELECT rolname FROM pg_roles WHERE oid = p.proowner) != n.nspname
        ORDER BY n.nspname, p.proname
    LOOP
        sql_stmt := format('ALTER PROCEDURE %I.%I(%s) OWNER TO %I',
            obj_rec.schemaname, obj_rec.procname, obj_rec.args, obj_rec.schemaname);
        RAISE NOTICE '%', sql_stmt;
        EXECUTE sql_stmt;
        processed := processed + 1;
    END LOOP;

    -- 8. Modify PACKAGE owners
    FOR obj_rec IN
        SELECT n.nspname AS schemaname, p.pkgname AS pkgname
        FROM pg_pkg p JOIN pg_namespace n ON p.pkgnamespace = n.oid
        WHERE n.nspname IN (SELECT rolname FROM pg_roles WHERE rolsuper = false)
          AND EXISTS (SELECT 1 FROM information_schema.schemata s WHERE s.schema_name = n.nspname)
          AND n.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast',
              'polar_catalog', 'sys', 'cron', 'wmsys', 'polar_feature_utils',
              'pg_bitmapindex', 'polar_dbms_stats', 'public')
          AND (SELECT rolname FROM pg_roles WHERE oid = p.pkgowner) != n.nspname
        ORDER BY n.nspname, p.pkgname
    LOOP
        sql_stmt := format('ALTER PACKAGE %I.%I OWNER TO %I',
            obj_rec.schemaname, obj_rec.pkgname, obj_rec.schemaname);
        RAISE NOTICE '%', sql_stmt;
        EXECUTE sql_stmt;
        processed := processed + 1;
    END LOOP;

    -- 9. Modify TYPE owners (user-defined composite types only)
    FOR obj_rec IN
        SELECT n.nspname AS schemaname, t.typname AS typename
        FROM pg_type t JOIN pg_namespace n ON t.typnamespace = n.oid
        WHERE n.nspname IN (SELECT rolname FROM pg_roles WHERE rolsuper = false)
          AND EXISTS (SELECT 1 FROM information_schema.schemata s WHERE s.schema_name = n.nspname)
          AND n.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast',
              'polar_catalog', 'sys', 'cron', 'wmsys', 'polar_feature_utils',
              'pg_bitmapindex', 'polar_dbms_stats', 'public')
          AND n.nsppkgns = 0
          AND t.typname NOT LIKE '\_%'
          AND t.typtype IN ('c', 'b')
          AND (t.typrelid = 0 OR EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = t.typrelid AND c.relkind = 'c'))
          AND (SELECT rolname FROM pg_roles WHERE oid = t.typowner) != n.nspname
        ORDER BY n.nspname, t.typname
    LOOP
        sql_stmt := format('ALTER TYPE %I.%I OWNER TO %I',
            obj_rec.schemaname, obj_rec.typename, obj_rec.schemaname);
        RAISE NOTICE '%', sql_stmt;
        EXECUTE sql_stmt;
        processed := processed + 1;
    END LOOP;

    -- 10. Modify SYNONYM owners
    FOR obj_rec IN
        SELECT n.nspname AS schemaname, s.synname AS synname,
            CASE WHEN n.nspname = 'public' THEN 'PUBLIC' ELSE n.nspname END AS new_owner
        FROM pg_synonym s JOIN pg_namespace n ON s.synnamespace = n.oid
        WHERE n.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast',
              'polar_catalog', 'sys', 'cron', 'wmsys', 'polar_feature_utils',
              'pg_bitmapindex', 'polar_dbms_stats')
          AND n.nsppkgns = 0
          AND (SELECT rolname FROM pg_roles WHERE oid = s.owner) !=
              CASE WHEN n.nspname = 'public' THEN 'PUBLIC' ELSE n.nspname END
        ORDER BY n.nspname, s.synname
    LOOP
        sql_stmt := format('ALTER SYNONYM %I.%I OWNER TO %I',
            obj_rec.schemaname, obj_rec.synname, obj_rec.new_owner);
        RAISE NOTICE '%', sql_stmt;
        EXECUTE sql_stmt;
        processed := processed + 1;
    END LOOP;

    RAISE NOTICE 'Total objects processed: %', processed;
END $$;

(Optional) Grant system privileges

System privileges are an advanced Oracle feature that primarily control DDL execution. In PolarDB, we recommend that you use a privileged account for DDL operations, so you generally do not need to migrate system privileges. If needed, run the following PL/SQL script in SQL*Plus to export system privilege GRANT statements from the DBA_SYS_PRIVS view to the grant_sys_priv.sql file, then run it in PolarDB.

Important
  • The query includes all roles in GRANTEE (not only non-system roles) because Oracle system roles may also be granted privileges manually, which must also be migrated. The query also includes the PUBLIC virtual role, which represents all users.

  • Before you run this command, log on to the PolarDB console and enable the polar_enable_system_privilege parameter on the cluster Parameters page.

Oracle 12c and later

SET HEADING OFF
SET TRIMSPOOL ON
SET TRIMOUT ON
SET PAGESIZE 0
SET LINESIZE 2000
SET SERVEROUTPUT ON
SPOOL /my/path/to/grant_sys_priv.sql

BEGIN
    FOR rec IN (
        SELECT
            'GRANT ' || PRIVILEGE || ' TO ' || GRANTEE ||
            CASE WHEN ADMIN_OPTION = 'YES' THEN ' WITH ADMIN OPTION' END || ';' AS ddl_script
        FROM DBA_SYS_PRIVS
        WHERE GRANTEE IN (
            SELECT USERNAME FROM DBA_USERS WHERE ORACLE_MAINTAINED = 'N'
            UNION
            SELECT ROLE FROM DBA_ROLES
            UNION
            SELECT 'PUBLIC' FROM DUAL
        )
        ORDER BY GRANTEE, PRIVILEGE
    ) LOOP
        DBMS_OUTPUT.PUT_LINE(rec.ddl_script);
    END LOOP;
END;
/

SPOOL OFF
EXIT

Oracle 11g

SET HEADING OFF
SET TRIMSPOOL ON
SET TRIMOUT ON
SET PAGESIZE 0
SET LINESIZE 2000
SET SERVEROUTPUT ON
SPOOL /my/path/to/grant_sys_priv.sql

BEGIN
    FOR rec IN (
        SELECT
            'GRANT ' || PRIVILEGE || ' TO ' || GRANTEE ||
            CASE WHEN ADMIN_OPTION = 'YES' THEN ' WITH ADMIN OPTION' END || ';' AS ddl_script
        FROM DBA_SYS_PRIVS
        WHERE GRANTEE IN (
            SELECT USERNAME FROM DBA_USERS WHERE USERNAME NOT IN (
                'SYS', 'SYSTEM', 'DBSNMP', 'SYSMAN', 'OUTLN',
                'CTXSYS', 'MDSYS', 'ORDSYS', 'ORDDATA', 'ORDPLUGINS', 'SI_INFORMTN_SCHEMA',
                'WMSYS', 'EXFSYS', 'LBACSYS', 'XDB', 'ANONYMOUS', 'XS$NULL',
                'OLAPSYS', 'MDDATA', 'OWBSYS', 'OWBSYS_AUDIT',
                'APEX_030200', 'APEX_040000', 'APEX_040100', 'APEX_040200', 'APEX_050000',
                'APEX_PUBLIC_USER', 'FLOWS_FILES', 'FLOWS_030000', 'FLOWS_040000',
                'SPATIAL_CSW_ADMIN_USR', 'SPATIAL_WFS_ADMIN_USR',
                'DIP', 'ORACLE_OCM', 'APPQOSSYS', 'MGMT_VIEW', 'SCOTT', 'HR',
                'TSMSYS', 'WKPROXY', 'WK_TEST', 'PERFSTAT', 'TRACESVR',
                'GSMADMIN_INTERNAL', 'GSMCATUSER', 'GSMUSER', 'SYSBACKUP', 'SYSDG', 'SYSKM'
            )
            AND USERNAME NOT LIKE 'APEX\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'FLOWS\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'ORA\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'ORACLE\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'SYS\_%' ESCAPE '\'
            UNION
            SELECT ROLE FROM DBA_ROLES
            UNION
            SELECT 'PUBLIC' FROM DUAL
        )
        ORDER BY GRANTEE, PRIVILEGE
    ) LOOP
        DBMS_OUTPUT.PUT_LINE(rec.ddl_script);
    END LOOP;
END;
/

SPOOL OFF
EXIT

(Optional) Set default roles

Default roles are an advanced Oracle feature that control which role privileges a user has by default after logon. In PolarDB, users automatically load privileges of all granted roles at logon, which matches Oracle's default behavior. Therefore, you generally do not need to perform this migration step.

If needed, run the following PL/SQL script in SQL*Plus to export ALTER USER ... DEFAULT ROLE statements to the default_role.sql file, then run it in PolarDB. The script automatically determines:

  • If the user's default role count = 0, the script emits ALTER USER ... DEFAULT ROLE NONE.

  • If the user's default role count = total role count, the script emits ALTER USER ... DEFAULT ROLE ALL.

  • Otherwise, the script emits the specific role list.

Important

Before you run this command, log on to the PolarDB console and enable the polar_enable_default_role parameter on the cluster Parameters page.

Oracle 12c and later

SET HEADING OFF
SET TRIMSPOOL ON
SET TRIMOUT ON
SET PAGESIZE 0
SET LINESIZE 2000
SET SERVEROUTPUT ON
SPOOL /my/path/to/default_role.sql

BEGIN
    FOR rec IN (
        WITH user_list AS (
            SELECT ROLE AS name FROM DBA_ROLES WHERE ORACLE_MAINTAINED = 'N'
            UNION
            SELECT USERNAME AS name FROM DBA_USERS WHERE ORACLE_MAINTAINED = 'N'
        ),
        role_stats AS (
            SELECT
                GRANTEE,
                COUNT(*) AS total_roles,
                SUM(CASE WHEN DEFAULT_ROLE = 'YES' THEN 1 ELSE 0 END) AS default_roles,
                LISTAGG(CASE WHEN DEFAULT_ROLE = 'YES' THEN GRANTED_ROLE END, ', ')
                    WITHIN GROUP (ORDER BY GRANTED_ROLE) AS default_role_list
            FROM DBA_ROLE_PRIVS
            WHERE GRANTEE IN (SELECT name FROM user_list)
              AND GRANTEE NOT IN ('PDBADMIN')
            GROUP BY GRANTEE
        )
        SELECT
            'ALTER USER ' || GRANTEE || ' DEFAULT ROLE ' ||
            CASE
                WHEN default_roles = 0 THEN 'NONE'
                WHEN default_roles = total_roles THEN 'ALL'
                ELSE default_role_list
            END || ';' AS ddl_script
        FROM role_stats
        ORDER BY GRANTEE
    ) LOOP
        DBMS_OUTPUT.PUT_LINE(rec.ddl_script);
    END LOOP;
END;
/

SPOOL OFF
EXIT

Oracle 11g

SET HEADING OFF
SET TRIMSPOOL ON
SET TRIMOUT ON
SET PAGESIZE 0
SET LINESIZE 2000
SET SERVEROUTPUT ON
SPOOL /my/path/to/default_role.sql

BEGIN
    FOR rec IN (
        WITH user_list AS (
            SELECT ROLE AS name FROM DBA_ROLES WHERE ROLE NOT IN (
                'CONNECT', 'RESOURCE', 'DBA', 'SELECT_CATALOG_ROLE',
                'EXECUTE_CATALOG_ROLE', 'DELETE_CATALOG_ROLE', 'EXP_FULL_DATABASE',
                'IMP_FULL_DATABASE', 'LOGSTDBY_ADMINISTRATOR', 'RECOVERY_CATALOG_OWNER',
                'SCHEDULER_ADMIN', 'HS_ADMIN_SELECT_ROLE', 'HS_ADMIN_EXECUTE_ROLE',
                'HS_ADMIN_ROLE', 'GLOBAL_AQ_USER_ROLE', 'OEM_ADVISOR', 'OEM_MONITOR',
                'MGMT_USER', 'DATAPUMP_EXP_FULL_DATABASE', 'DATAPUMP_IMP_FULL_DATABASE',
                'ADM_PARALLEL_EXECUTE_TASK', 'GATHER_SYSTEM_STATISTICS',
                'OPTIMIZER_PROCESSING_RATE', 'ACCESS_CATALOG_ROLE', 'JAVAUSERPRIV',
                'JAVAIDPRIV', 'JAVASYSPRIV', 'JAVADEBUGPRIV', 'EJBCLIENT', 'JMXSERVER',
                'JAVA_ADMIN', 'JAVA_DEPLOY', 'AUTHENTICATEDUSER', 'XDB_SET_INVOKER',
                'XDB_WEBSERVICES', 'XDB_WEBSERVICES_WITH_PUBLIC', 'XDB_WEBSERVICES_OVER_HTTP',
                'XDBADMIN', 'XDB_WEBSERVICES_OVER_HTTPS', 'CTXAPP', 'ORDADMIN',
                'WM_ADMIN_ROLE', 'OLAP_USER', 'OLAP_DBA', 'OLAP_XS_ADMIN',
                'CSW_USR_ROLE', 'WFS_USR_ROLE', 'SPATIAL_CSW_ADMIN',
                'SPATIAL_WFS_ADMIN', 'LBAC_DBA', 'APEX_ADMINISTRATOR_ROLE',
                'FLOWS_FILES_ROLE', 'AQ_ADMINISTRATOR_ROLE', 'AQ_USER_ROLE',
                'CWM_USER', 'DBFS_ROLE', 'OLAPI_TRACE_USER', 'OWB$CLIENT',
                'OWB_DESIGNCENTER_VIEW', 'OWB_USER'
            )
            AND ROLE NOT LIKE 'APEX\_%' ESCAPE '\'
            AND ROLE NOT LIKE 'FLOWS\_%' ESCAPE '\'
            AND ROLE NOT LIKE 'ORA\_%' ESCAPE '\'
            AND ROLE NOT LIKE 'ORACLE\_%' ESCAPE '\'
            AND ROLE NOT LIKE 'SYS\_%' ESCAPE '\'
            UNION
            SELECT USERNAME AS name FROM DBA_USERS WHERE USERNAME NOT IN (
                'SYS', 'SYSTEM', 'DBSNMP', 'SYSMAN', 'OUTLN',
                'CTXSYS', 'MDSYS', 'ORDSYS', 'ORDDATA', 'ORDPLUGINS', 'SI_INFORMTN_SCHEMA',
                'WMSYS', 'EXFSYS', 'LBACSYS', 'XDB', 'ANONYMOUS', 'XS$NULL',
                'OLAPSYS', 'MDDATA', 'OWBSYS', 'OWBSYS_AUDIT',
                'APEX_030200', 'APEX_040000', 'APEX_040100', 'APEX_040200', 'APEX_050000',
                'APEX_PUBLIC_USER', 'FLOWS_FILES', 'FLOWS_030000', 'FLOWS_040000',
                'SPATIAL_CSW_ADMIN_USR', 'SPATIAL_WFS_ADMIN_USR',
                'DIP', 'ORACLE_OCM', 'APPQOSSYS', 'MGMT_VIEW', 'SCOTT', 'HR',
                'TSMSYS', 'WKPROXY', 'WK_TEST', 'PERFSTAT', 'TRACESVR',
                'GSMADMIN_INTERNAL', 'GSMCATUSER', 'GSMUSER', 'SYSBACKUP', 'SYSDG', 'SYSKM'
            )
            AND USERNAME NOT LIKE 'APEX\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'FLOWS\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'ORA\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'ORACLE\_%' ESCAPE '\'
            AND USERNAME NOT LIKE 'SYS\_%' ESCAPE '\'
        ),
        role_stats AS (
            SELECT
                GRANTEE,
                COUNT(*) AS total_roles,
                SUM(CASE WHEN DEFAULT_ROLE = 'YES' THEN 1 ELSE 0 END) AS default_roles,
                LISTAGG(CASE WHEN DEFAULT_ROLE = 'YES' THEN GRANTED_ROLE END, ', ')
                    WITHIN GROUP (ORDER BY GRANTED_ROLE) AS default_role_list
            FROM DBA_ROLE_PRIVS
            WHERE GRANTEE IN (SELECT name FROM user_list)
              AND GRANTEE NOT IN ('PDBADMIN')
            GROUP BY GRANTEE
        )
        SELECT
            'ALTER USER ' || GRANTEE || ' DEFAULT ROLE ' ||
            CASE
                WHEN default_roles = 0 THEN 'NONE'
                WHEN default_roles = total_roles THEN 'ALL'
                ELSE default_role_list
            END || ';' AS ddl_script
        FROM role_stats
        ORDER BY GRANTEE
    ) LOOP
        DBMS_OUTPUT.PUT_LINE(rec.ddl_script);
    END LOOP;
END;
/

SPOOL OFF
EXIT

(Optional) Create PROFILEs

PROFILEs are an advanced Oracle feature for password complexity checks, expiration policies, and similar controls. In PolarDB, users and passwords are typically managed from the console, so you generally do not need to migrate PROFILEs.

If needed, run the following PL/SQL script in SQL*Plus to retrieve PROFILEs for all users. The script emits four files: profile_verify_functions.sql, create_profiles.sql, alter_default_profile.sql, and alter_user_profile.sql. Transfer the files to the PolarDB side and run them in order by using psql to migrate Oracle PROFILEs to PolarDB.

Note
  • Password parameters only: The script migrates only PASSWORD-related parameters (PASSWORD_LIFE_TIME, PASSWORD_REUSE_TIME, PASSWORD_REUSE_MAX, PASSWORD_VERIFY_FUNCTION, PASSWORD_GRACE_TIME, PASSWORD_LOCK_TIME, and FAILED_LOGIN_ATTEMPTS). Resource limits (such as SESSIONS_PER_USER, CPU_PER_SESSION, and CONNECT_TIME) are not migrated because the PolarDB kernel does not support them.

  • System PROFILEs excluded: ORA_CIS_PROFILE, ORA_STIG_PROFILE, and MONITORING_PROFILE are Oracle built-in PROFILEs and are not migrated.

  • Built-in verify functions excluded: ORA12C_VERIFY_FUNCTION and ORA12C_STIG_VERIFY_FUNCTION are Oracle built-in functions; they are not exported because PolarDB already provides them.

  • Execution order: Run the files in the order Step 1 → 2 → 3 → 4 because Step 2 may reference the verify functions created in Step 1.

Oracle 12c and later

-- ==================================================================
-- Step 1: Export password verify functions referenced by PROFILEs
-- Description: Query DBA_SOURCE to get the full definition of custom verify functions referenced by PROFILEs.
-- Excludes Oracle built-in functions (ORA12C_VERIFY_FUNCTION, ORA12C_STIG_VERIFY_FUNCTION).
-- ==================================================================
SET SERVEROUTPUT ON SIZE UNLIMITED
SET LINESIZE 32767
SET LONG 2000000000
SET LONGCHUNKSIZE 2000000000
SET PAGESIZE 0
SET FEEDBACK OFF
SET HEADING OFF
SPOOL profile_verify_functions.sql

SELECT 'CREATE OR REPLACE ' || LISTAGG(TEXT, CHR(10)) WITHIN GROUP (ORDER BY LINE) AS ddl_script
FROM DBA_SOURCE
WHERE NAME IN (
    SELECT DISTINCT LIMIT
    FROM DBA_PROFILES
    WHERE RESOURCE_NAME = 'PASSWORD_VERIFY_FUNCTION'
      AND LIMIT IS NOT NULL
      AND LIMIT <> 'NULL'
      AND LIMIT NOT IN ('ORA12C_VERIFY_FUNCTION', 'ORA12C_STIG_VERIFY_FUNCTION')
)
AND TYPE = 'FUNCTION'
AND OWNER = 'SYS'
GROUP BY NAME, TYPE, OWNER;

SPOOL OFF

-- ==================================================================
-- Step 2: Create non-DEFAULT PROFILEs
-- Description: Read password-related parameters from DBA_PROFILES and generate CREATE PROFILE statements.
-- Excludes system PROFILEs (DEFAULT, ORA_CIS_PROFILE, ORA_STIG_PROFILE, MONITORING_PROFILE).
-- Migrates only password policy parameters; resource limits are not migrated.
-- ==================================================================
SPOOL create_profiles.sql

SELECT DISTINCT 'CREATE PROFILE ' || PROFILE || ' LIMIT '
         || LISTAGG(RESOURCE_NAME || ' ' || LIMIT, ' ') WITHIN GROUP (ORDER BY RESOURCE_NAME)
         || ';' AS ddl_script
  FROM DBA_PROFILES WHERE RESOURCE_NAME IN (
'PASSWORD_LIFE_TIME',
'PASSWORD_REUSE_TIME',
'PASSWORD_REUSE_MAX',
'PASSWORD_VERIFY_FUNCTION',
'PASSWORD_GRACE_TIME',
'PASSWORD_LOCK_TIME',
'FAILED_LOGIN_ATTEMPTS'
) AND PROFILE NOT IN ('DEFAULT', 'ORA_CIS_PROFILE', 'ORA_STIG_PROFILE', 'MONITORING_PROFILE')
GROUP BY PROFILE;

SPOOL OFF

-- ==================================================================
-- Step 3: Update the DEFAULT PROFILE
-- Description: The target already has a DEFAULT profile, so use ALTER PROFILE instead of CREATE.
-- ==================================================================
SPOOL alter_default_profile.sql

SELECT DISTINCT 'ALTER PROFILE DEFAULT LIMIT '
         || LISTAGG(RESOURCE_NAME || ' ' || LIMIT, ' ') WITHIN GROUP (ORDER BY RESOURCE_NAME)
         || ';' AS ddl_script
  FROM DBA_PROFILES WHERE RESOURCE_NAME IN (
'PASSWORD_LIFE_TIME',
'PASSWORD_REUSE_TIME',
'PASSWORD_REUSE_MAX',
'PASSWORD_VERIFY_FUNCTION',
'PASSWORD_GRACE_TIME',
'PASSWORD_LOCK_TIME',
'FAILED_LOGIN_ATTEMPTS'
) AND PROFILE = 'DEFAULT'
GROUP BY PROFILE;

SPOOL OFF

-- ==================================================================
-- Step 4: Bind users to PROFILEs
-- Description: Migrate the user-profile binding for non-system source users to the target.
-- Filter: ORACLE_MAINTAINED = 'N' excludes system users.
-- ==================================================================
SPOOL alter_user_profile.sql

SELECT 'ALTER USER ' || USERNAME || ' PROFILE ' || PROFILE || ';' AS ddl_script
FROM DBA_USERS
WHERE PROFILE NOT IN ('ORA_CIS_PROFILE', 'ORA_STIG_PROFILE', 'MONITORING_PROFILE')
  AND ORACLE_MAINTAINED = 'N'
ORDER BY USERNAME;

SPOOL OFF

Oracle 11g

Oracle 11g does not have the ORACLE_MAINTAINED column. Therefore, Step 4 uses a predefined list to filter system users. Steps 1, 2, and 3 are identical to the Oracle 12c and later version; only Step 4 differs.

-- ==================================================================
-- Steps 1-3: Identical to the Oracle 12c and later version. Not repeated here.
-- ==================================================================

-- ==================================================================
-- Step 4: Bind users to PROFILEs (Oracle 11g)
-- Description: Uses an explicit system user list instead of the ORACLE_MAINTAINED column.
-- ==================================================================
SPOOL alter_user_profile.sql

SELECT 'ALTER USER ' || USERNAME || ' PROFILE ' || PROFILE || ';' AS ddl_script
FROM DBA_USERS
WHERE PROFILE NOT IN ('ORA_CIS_PROFILE', 'ORA_STIG_PROFILE', 'MONITORING_PROFILE')
  AND USERNAME NOT IN (
    'SYS','SYSTEM','DBSNMP','SYSMAN','OUTLN',
    'CTXSYS','MDSYS','ORDSYS','ORDDATA','ORDPLUGINS','SI_INFORMTN_SCHEMA',
    'WMSYS','EXFSYS','LBACSYS','XDB','ANONYMOUS','XS$NULL',
    'OLAPSYS','MDDATA',
    'OWBSYS','OWBSYS_AUDIT',
    'APEX_030200','APEX_040000','APEX_040100','APEX_040200','APEX_050000',
    'APEX_PUBLIC_USER','FLOWS_FILES','FLOWS_030000','FLOWS_040000',
    'SPATIAL_CSW_ADMIN_USR','SPATIAL_WFS_ADMIN_USR',
    'DIP','ORACLE_OCM','APPQOSSYS','MGMT_VIEW','SCOTT','HR','TSMSYS',
    'WKPROXY','WK_TEST','PERFSTAT','TRACESVR',
    'GSMADMIN_INTERNAL','GSMCATUSER','GSMUSER','SYSBACKUP','SYSDG','SYSKM'
  )
  AND USERNAME NOT LIKE 'APEX\_%' ESCAPE '\'
  AND USERNAME NOT LIKE 'FLOWS\_%' ESCAPE '\'
  AND USERNAME NOT LIKE 'ORA\_%' ESCAPE '\'
  AND USERNAME NOT LIKE 'ORACLE\_%' ESCAPE '\'
  AND USERNAME NOT LIKE 'SYS\_%' ESCAPE '\'
ORDER BY USERNAME;

SPOOL OFF