如果有一种方法可以使用 JDBC 在语法和语义上编译(而不是执行)查询?
If there a way to compile (not execute) a query syntaxicly and semanticly with JDBC?
我有很多包含语法错误的查询(没有单元测试,但这是另一个问题),我想大量检查是否没有错误。
为此,我首先做了以下工作:
String q = ...; // some query
try (PreparedStatement stmt = connection.prepareStatement(q)) {
final ParameterMetaData pmd = stmt.getParameterMetaData();
for (int i = 1; i <= pmd.getParameterCount(); ++i) {
stmt.setNull(i, java.sql.Types.NULL);
}
stmt.execute();
} catch (SQLException e) {
...
} finally {
connection.rollback();
}
它有效,但后来我遇到了这样的错误:http://www.oracle-error.com/11g/ORA-30081.html
基本上,在我查询的某处,我有:
select *
from table T
where id = ? or ( ? - INTERVAL '1' DAY ) between date_start and date_end
如果我执行相同的查询,将 ?
替换为 NULL
,在 TOAD 中,我会遇到同样的错误。
ParameterMetaData
也没有帮助,因为它不存储我想要的信息(例如:Oracle 期望的参数)。
是否有一些解决方案可以在语法和语义上编译查询(检查是否缺少列等)并忽略参数?
截至目前,我将 ?
替换为 NULL
,除非在“?”之后我发现了一些约会的东西,我用 sysdate
.
例如:
select *
from table T
where id = NULL or ( sysdate - INTERVAL '1' DAY ) between date_start and date_end
不是直接通过JDBC,而是可以间接的; heavily inspired by this,你可以这样做:
String q = ...; // some query
try (PreparedStatement stmt = connection.prepareStatement("declare c integer;
begin
c := dbms_sql.open_cursor;
dbms_sql.parse(c,?,dbms_sql.native);
dbms_sql.close_cursor(c);
end;")) {
stmt.setString(1, q.replace("?", ":b0"));
stmt.execute();
} catch (SQLException e) {
...
}
您准备的语句现在是一个匿名块,单个绑定变量现在是您要验证的原始查询。您不需要了解有关查询参数的任何信息。 replace
将 JDBC ?
占位符转换为通用 :b0
绑定变量,因此解析器不会反对它们。
您可以更高级,将每个占位符替换为不同的绑定变量(:b0
、:b1
)等,但我认为通常没有必要这样做。当然,这种粗略的替换也可能会修改字符串文字,这可能是您需要考虑的事情;正则表达式方法会更健壮。
另一种尝试方法可能是使用 Oracle 和其他一些 DBMS 中可用的 EXPLAIN PLAN
语句(形式可能略有不同)。在你的声明前添加 'EXPLAIN PLAN FOR '
和 execute()
(无需准备)。原来的语句实际上不会运行,但是会被解析编译,不需要绑定任何参数
但在某些情况下,它可能仍会因未类型化的参数标记而阻塞。
我有很多包含语法错误的查询(没有单元测试,但这是另一个问题),我想大量检查是否没有错误。
为此,我首先做了以下工作:
String q = ...; // some query
try (PreparedStatement stmt = connection.prepareStatement(q)) {
final ParameterMetaData pmd = stmt.getParameterMetaData();
for (int i = 1; i <= pmd.getParameterCount(); ++i) {
stmt.setNull(i, java.sql.Types.NULL);
}
stmt.execute();
} catch (SQLException e) {
...
} finally {
connection.rollback();
}
它有效,但后来我遇到了这样的错误:http://www.oracle-error.com/11g/ORA-30081.html
基本上,在我查询的某处,我有:
select *
from table T
where id = ? or ( ? - INTERVAL '1' DAY ) between date_start and date_end
如果我执行相同的查询,将 ?
替换为 NULL
,在 TOAD 中,我会遇到同样的错误。
ParameterMetaData
也没有帮助,因为它不存储我想要的信息(例如:Oracle 期望的参数)。
是否有一些解决方案可以在语法和语义上编译查询(检查是否缺少列等)并忽略参数?
截至目前,我将 ?
替换为 NULL
,除非在“?”之后我发现了一些约会的东西,我用 sysdate
.
例如:
select *
from table T
where id = NULL or ( sysdate - INTERVAL '1' DAY ) between date_start and date_end
不是直接通过JDBC,而是可以间接的; heavily inspired by this,你可以这样做:
String q = ...; // some query
try (PreparedStatement stmt = connection.prepareStatement("declare c integer;
begin
c := dbms_sql.open_cursor;
dbms_sql.parse(c,?,dbms_sql.native);
dbms_sql.close_cursor(c);
end;")) {
stmt.setString(1, q.replace("?", ":b0"));
stmt.execute();
} catch (SQLException e) {
...
}
您准备的语句现在是一个匿名块,单个绑定变量现在是您要验证的原始查询。您不需要了解有关查询参数的任何信息。 replace
将 JDBC ?
占位符转换为通用 :b0
绑定变量,因此解析器不会反对它们。
您可以更高级,将每个占位符替换为不同的绑定变量(:b0
、:b1
)等,但我认为通常没有必要这样做。当然,这种粗略的替换也可能会修改字符串文字,这可能是您需要考虑的事情;正则表达式方法会更健壮。
另一种尝试方法可能是使用 Oracle 和其他一些 DBMS 中可用的 EXPLAIN PLAN
语句(形式可能略有不同)。在你的声明前添加 'EXPLAIN PLAN FOR '
和 execute()
(无需准备)。原来的语句实际上不会运行,但是会被解析编译,不需要绑定任何参数
但在某些情况下,它可能仍会因未类型化的参数标记而阻塞。