Невозможно подготовить оператор Postgres в Java (java.sql.PreparedStatement) - PullRequest
3 голосов
/ 07 июня 2019

Рассмотрим два оператора, которые я могу отправить на сервер с Java.

  1. Простой SQL : Обычно это оператор вставки.
insert into table_things (thing_1_value, thing_2_value) values(?, ?);
  1. PlPgSQL : Я хочу избежать обходов базы данных, выполнив вход в базу данных. Нам не разрешается использовать хранимые процедуры или функции в базе данных (причины кажутся действительными).
do $$
declare
    my_thing1 varchar(100) = ?;
    my_thing2 varchar(100) = ?;
begin
    insert into table_things
    (
          thing_1_value
        , thing_2_value
    )
    values
    (
          my_thing1
        , my_thing2
    )
    ;
end
$$;

Код, который выполняет эти операторы, представлен ниже в тестовых случаях Java8:

package com.somecompany.someservice.test.database;

import org.apache.commons.dbcp2.BasicDataSource;
import org.junit.Assert;
import org.junit.Test;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Types;

public class PreparedStatementDatabaseTest {
    private static final String CONNECTION_URI = "jdbc:postgresql://localhost:5432/somedb?user=someuser&password=somepass";

    private static final String PLPGSQL_STATEMENT = "" +
            "do $$\n" +
            "declare\n" +
            "    my_thing1 varchar(100) = ?;\n" +
            "    my_thing2 varchar(100) = ?;\n" +
            "begin\n" +
            "    insert into table_things\n" +
            "    (\n" +
            "          thing_1_value\n" +
            "        , thing_2_value\n" +
            "    )\n" +
            "    values\n" +
            "    (\n" +
            "          my_thing1\n" +
            "        , my_thing2\n" +
            "    )\n" +
            "    ;\n" +
            "end\n" +
            "$$;";

    private static final String EASY_SQL_STATEMENT = "insert into table_things (thing_1_value, thing_2_value) values(?, ?);";

    @Test
    public void testPlpgsqlStatement() throws Exception {
        Class.forName("org.postgresql.Driver");
        BasicDataSource basicDataSource = new BasicDataSource();
        basicDataSource.setUrl(CONNECTION_URI);
        Connection conn = basicDataSource.getConnection();
        PreparedStatement statement = conn.prepareStatement(PLPGSQL_STATEMENT, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
        statement.setObject(1, "hello", Types.VARCHAR);
        statement.setObject(2, "world", Types.VARCHAR);
        boolean isResultSet = statement.execute();
        conn.close();
        Assert.assertFalse(isResultSet);
    }

    @Test
    public void testEasySqlStatement() throws Exception {
        Class.forName("org.postgresql.Driver");
        BasicDataSource basicDataSource = new BasicDataSource();
        basicDataSource.setUrl(CONNECTION_URI);
        Connection conn = basicDataSource.getConnection();
        PreparedStatement statement = conn.prepareStatement(EASY_SQL_STATEMENT, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
        statement.setObject(1, "hello", Types.VARCHAR);
        statement.setObject(2, "world", Types.VARCHAR);
        boolean isResultSet = statement.execute();
        conn.close();
        Assert.assertFalse(isResultSet);
    }
}

testEasySqlStatement работает, но testPlpgsqlStatement выдает исключение:

org.postgresql.util.PSQLException: The column index is out of range: 1, number of columns: 0.

    at org.postgresql.core.v3.SimpleParameterList.bind(SimpleParameterList.java:65)
    at org.postgresql.core.v3.SimpleParameterList.setStringParameter(SimpleParameterList.java:128)
    at org.postgresql.jdbc.PgPreparedStatement.bindString(PgPreparedStatement.java:996)
    at org.postgresql.jdbc.PgPreparedStatement.setString(PgPreparedStatement.java:326)
    at org.postgresql.jdbc.PgPreparedStatement.setObject(PgPreparedStatement.java:528)
    at org.postgresql.jdbc.PgPreparedStatement.setObject(PgPreparedStatement.java:881)
    at org.apache.commons.dbcp2.DelegatingPreparedStatement.setObject(DelegatingPreparedStatement.java:185)
    at org.apache.commons.dbcp2.DelegatingPreparedStatement.setObject(DelegatingPreparedStatement.java:185)
    at com.somecompany.someservicetest.database.PreparedStatementDatabaseTest.testPlpgsqlStatement(PreparedStatementDatabaseTest.java:44)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

Вопрос : как я могу отправить код типа PLPGSQL_STATEMENT в базу данных Postgres?

Я мог бы сделать это, но это плохая практика из-за риска внедрения SQL:

    @Test
    public void testSqlInjectionRisk() throws Exception {
        String hello = "hello-testSqlInjectionRisk";
        String world = "world-testSqlInjectionRisk";

        String PLPGSQL_STATEMENT = "" +
                "do $$\n" +
                "declare\n" +
                "    my_thing1 varchar(100) = '" + hello + "';\n" +
                "    my_thing2 varchar(100) = '" + world + "';\n" +
                "begin\n" +
                "    insert into table_things\n" +
                "    (\n" +
                "          thing_1_value\n" +
                "        , thing_2_value\n" +
                "    )\n" +
                "    values\n" +
                "    (\n" +
                "          my_thing1\n" +
                "        , my_thing2\n" +
                "    )\n" +
                "    ;\n" +
                "end\n" +
                "$$;";

        Class.forName("org.postgresql.Driver");
        BasicDataSource basicDataSource = new BasicDataSource();
        basicDataSource.setUrl(CONNECTION_URI);
        Connection conn = basicDataSource.getConnection();
        PreparedStatement statement = conn.prepareStatement(PLPGSQL_STATEMENT, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
        boolean isResultSet = statement.execute();
        conn.close();
        Assert.assertFalse(isResultSet);

Повторный вопрос : Есть ли проблема с тем, как я пытаюсь подготовить PLPGSQL_STATEMENT? Можно ли подготовить PLPGSQL_STATEMENT?

Обновление : @Izruo указал, что я должен использовать prepareCall, и это, кажется, часть ответа. Но, к сожалению, следующий код завершается ошибкой с тем же исключением:

    @Test
    public void testEasySqlStatement2() throws Exception {
        final String SQL_STATEMENT = "" +
                "do $$\n" +
                "declare\n" +
                "    x varchar(100) = ?;\n" +
                "    y varchar(100) = ?;\n" +
                "begin\n" +
                "    insert into table_things\n" +
                "    (\n" +
                "          my_thing1\n" +
                "        , my_thing2\n" +
                "    )\n" +
                "    values\n" +
                "    (\n" +
                "          x\n" +
                "        , y\n" +
                "    )\n" +
                "    ;\n" +
                "end\n" +
                "$$;";

        Class.forName("org.postgresql.Driver");
        BasicDataSource basicDataSource = new BasicDataSource();
        basicDataSource.setUrl(CONNECTION_URI);
        System.out.println(SQL_STATEMENT);
        Connection conn = basicDataSource.getConnection();
        CallableStatement statement = conn.prepareCall(SQL_STATEMENT);
        statement.setObject(1, "hello", Types.VARCHAR);
        statement.setObject(2, "world", Types.VARCHAR);
        boolean isResultSet = statement.execute();
        conn.close();
        Assert.assertFalse(isResultSet);

Если я скопирую оператор sql, напечатанный System.out.println(SQL_STATEMENT);, в DataGrip (IDE базы данных JetBrains) и запустю его, то DataGrip попросит меня заполнить два значения параметров (для двух вопросительных знаков) и успешно выполнит sql заявление. Другими словами, код plpgsql синтаксически действителен (после замены параметров).

Кажется, здесь есть три возможности, и я не могу сказать, какая из них верна:

  1. Эта функция (создание CallableStatement / PreparedStatement с переменными plpgsql в ней) не поддерживается.
  2. Эта функция поддерживается, но я делаю это неправильно.
  3. Функциональность поддерживается, я правильно ее использую, но есть ошибка.

Ответы [ 3 ]

6 голосов
/ 11 июня 2019

Вы не можете вызвать динамическую процедуру напрямую.

Сначала необходимо создать процедуру (вручную или динамически с помощью вызова Statement), а затем вызвать процедуру по имени.

Statement stmt = conn.createStatement();
stmt.execute("CREATE OR REPLACE FUNCTION myfunc(x text, y text) RETURNS refcursor AS '"
        + "do $$\n"
        + "begin\n"
        + "    insert into table_things\n"
        + "    (\n"
        + "          my_thing1\n"
        + "        , my_thing2\n"
        + "    )\n"
        + "    values\n"
        + "    (\n"
        + "          x\n"
        + "        , y\n"
        + "    )\n"
        + "    ;\n"
        + "end\n"
        + "$$;"
        + "' language plpgsql");
stmt.close();

// We must be inside a transaction for cursors to work.
conn.setAutoCommit(false);

// Procedure call.
CallableStatement proc = conn.prepareCall("{ call myfunc(?,?)}");
CallableStatement statement = conn.prepareCall(SQL_STATEMENT);
statement.setObject(1, "hello", Types.VARCHAR);
statement.setObject(2, "world", Types.VARCHAR);
3 голосов
/ 17 июня 2019
    try (Connection con = DriverManager.getConnection(dbConnectionString, user, password);
         Statement st = con.createStatement();
         ResultSet rs = st.executeQuery("SELECT  * FROM public.\"Airplanes\" ")) {
        while (rs.next()) {

            //use result
        }

    } catch (SQLException ex) {

        //handle exception
    }

    return results;
}
0 голосов
/ 20 июня 2019

Хотя ответ Mạnh Quyết Nguyễn находится на правильном пути, он лишен объяснений, и предлагаемый обходной путь не подходит в моем случае (я думаю, что в большинстве случаев).

Я получил авторитетный ответс postgresql.org.

вы пытались добавить вопросительные знаки в место, где они не интерпретируются как параметры.

В основном вы написали:

SELECT 'letмне сказать??вам ';

Это совершенно правильный запрос, который имеет нулевые входные параметры и вернет:

"позвольте мне сказать? вам"

Он не имеет вводапараметры, потому что знаки вопроса, которые вы написали, находятся внутри строкового литерала.

$$ ... $$ в вашем операторе DO также обозначает строковый литерал.

Это прискорбно,насколько я могу судить, это означает, что весь язык PL / pgSQL запрещен, если вам нужно передать параметры в этот код PL / pgSQL.(Если, конечно, вы не компилируете процедуры или функции PL / pgSQL на лету или как часть разработки схемы).Похоже, я не могу отправить PL / pgSQL 'script' в базу данных вместе с параметрами.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...