public void createOrUpdateTableIndexes(String table, List<ModelColumn> columns,
                                           boolean isAdditionalTable) throws SQLException {
        final var jdbcClient = (JDBCClient) client;

        // Additional table's id is a many-to-one relation to the main table's id,
        // and thus can not be primary key, but a simple index.
        if (isAdditionalTable) {
            final var index = "idx_" + Math.abs((table + "_" + JDBCTableInstaller.ID_COLUMN).hashCode());
            if (!jdbcClient.indexExists(table, index)) {
                executeSQL(
                    new SQLBuilder("CREATE INDEX ")
                        .append(index)
                        .append(" ON ").append(table)
                        .append("(")
                        .append(ID_COLUMN)
                        .append(")")
                );
            }
        }

        if (!isAdditionalTable) {
            final var index = "idx_" + Math.abs((table + "_" + JDBCTableInstaller.TABLE_COLUMN).hashCode());
            if (!jdbcClient.indexExists(table, index)) {
                executeSQL(
                    new SQLBuilder("CREATE INDEX ")
                        .append(index)
                        .append(" ON ")
                        .append(table)
                        .append("(")
                        .append(JDBCTableInstaller.TABLE_COLUMN)
                        .append(")")
                );
            }
        }

        final var columnsMissingIndex =
            columns
                .stream()
                .filter(ModelColumn::shouldIndex)
                .filter(it -> it.getLength() < 256)
                .map(ModelColumn::getColumnName)
                .map(ColumnName::getStorageName)
                .collect(toList());
        for (var column : columnsMissingIndex) {
            final var index = "idx_" + Math.abs((table + "_" + column).hashCode());
            if (!jdbcClient.indexExists(table, index)) {
                executeSQL(
                    new SQLBuilder("CREATE INDEX ")
                        .append(index)
                        .append(" ON ").append(table).append("(")
                        .append(column)
                        .append(")")
                );
            }
        }

        final var columnNames =
            columns
                .stream()
                .map(ModelColumn::getColumnName)
                .map(ColumnName::getStorageName)
                .collect(toSet());
        for (final var modelColumn : columns) {
            for (final var compositeIndex : modelColumn.getSqlDatabaseExtension().getIndices()) {
                final var multiColumns = Arrays.asList(compositeIndex.getColumns());
                // Don't create composite index on the additional table if it doesn't contain all needed columns.
                if (isAdditionalTable && !columnNames.containsAll(multiColumns)) {
                    continue;
                }
                final var index = "idx_" + Math.abs((table + "_" + String.join("_", multiColumns)).hashCode());
                if (jdbcClient.indexExists(table, index)) {
                    continue;
                }
                executeSQL(
                    new SQLBuilder("CREATE INDEX ")
                        .append(index)
                        .append(" ON ")
                        .append(table)
                        .append(multiColumns.stream().collect(joining(", ", " (", ")")))
                );
            }
        }
    }
JDBC table installer, uses standard SQL to create tables, indices, and views.
oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/JDBCTableInstaller.java