您好,我是小DAI,专注于数据库管理员相关的技术问答,请问有什么可以帮您?

SQLScript 代码分析器

SQLScript 代码分析器由两个内置过程组成,用于扫描 CREATE FUNCTIONCREATE PROCEDURE 语句,并搜索指示代码质量、安全性或性能问题的模式。

接口

列出可用规则的视图 SQLSCRIPT_ANALYZER_RULES 通过以下方式定义: 列名称类型RULE_NAMESPACEVARCHAR(16)RULE_NAMEVARCHAR(64)类别VARCHAR(16)SHORT_DESCRIPTIONVARCHAR(256)LONG_DESCRIPTIONNVARCHAR(5000)建议NVARCHAR(5000) 

过程 ANALYZE_SQLSCRIPT_DEFINITION

ANALYZE_SQLSCRIPT_DEFINITION 过程分析尚未创建的单个过程或函数的源代码。如果过程或函数参考不存在的对象,则无法执行分析。

示例代码CREATE PROCEDURE ANALYZE_SQLSCRIPT_DEFINITION( IN OBJECT_DEFINITION NCLOB, IN RULES TABLE( RULE_NAMESPACE VARCHAR(16), RULE_NAME VARCHAR(64), CATEGORY VARCHAR(16) ), OUT FINDINGS TABLE( RULE_NAMESPACE VARCHAR(16), RULE_NAME VARCHAR(64), CATEGORY VARCHAR(16), SHORT_DESCRIPTION NVARCHAR(256), START_POSITION INT, END_POSITION INT )) AS BUILTIN;

参数

参数描述OBJECT_DEFINITION包含要分析的 SQLScript 函数或过程的数据定义语言 (DDL) 字符串。规则要用于分析的规则。可以从视图 SQLSCRIPT_ANALYZER_RULES 中检索可用规则。FINDINGS列出分析期间发现的潜在问题 

在创建过程或函数之前检测问题

用户创建过程或函数时,可能会出现潜在问题。通过使用 DDL 语句执行 ANALYZE_SQLSCRIPT_DEFINITION 过程,将生成一个结果集,其中包括检测到的问题列表(包括警告和错误)以及任何规则应用程序结果。

现在,可以在 SQLScript 代码分析器实际创建之前检测执行 DDL 语句期间出现的警告消息。

以下代码示例演示了在使用 ANALYZE_SQLSCRIPT_DEFINITION 过程创建潜在问题之前如何分析 SQLScript 函数是否存在潜在问题:

DO BEGIN

RULES = SELECT RULE_NAMESPACE, RULE_NAME, CATEGORY FROM SQLSCRIPT_ANALYZER_RULES;

CALL ANALYZE_SQLSCRIPT_DEFINITION('

CREATE FUNCTION sudf_select_into()

RETURNS val INT

AS BEGIN

SELECT 123 INTO val FROM dummy;

END;', :RULES, RES);

SELECT * FROM :RES;

END;

('INTERNAL', None, 'WARNING', 'Not recommended feature: Using SELECT INTO in Scalar UDF', 72, None)

示例结果:

RULE_NAMESPACERULE_NAME类别SHORT_DESCRIPTIONSTART_POSITIONEND_POSITION内部没有警告不推荐的功能:在标量用户定义函数 (UDF) 中使用 SELECT INTO72没有 

检测到警告时,将与其他应用规则的结果(如果有)一起显示。

RULE_NAMESPACERULE_NAME类别SHORT_DESCRIPTIONSTART_POSITIONEND_POSITION内部没有警告<警告消息>位置Position 或 null(如果不存在)SAP<规则名称><规则类别><规则描述>位置位置 

如果在 SQLScript 编译期间发生错误,将报告如下:

RULE_NAMESPACERULE_NAME类别SHORT_DESCRIPTIONSTART_POSITIONEND_POSITION内部没有ERROR<错误消息>位置Position 或 null(如果不存在) 

限制

  • 不会捕获 CALL 语句执行期间的运行时错误或警告。

  • 当同时发生错误时,结果中不会显示警告。

    规则

    SAP 目前提供的以下规则可用:

    列:2 已选择全部清除 规则名称类别 规则名称类别UNNECESSARY_VARIABLE一致性UNUSED_VARIABLE_VALUE一致性UNCHECKED_SQL_INJECTION_SAFETY安全SINGLE_SPACE_LITERAL一致性COMMIT_OR_ROLLBACK_IN_DYNAMIC_SQL样式USE_OF_SELECT_IN_SCALAR_UDF性能USE_OF_UNASSIGNED_SCALAR_VARIABLE一致性DML_STATEMENTS_IN_LOOPS性能USE_OF_CE_FUNCTIONS性能USE_OF_DYNAMIC_SQL性能ROW_COUNT_AFTER_SELECT行为ROW_COUNT_AFTER_DYNAMIC_SQL行为USE_OF_SET_SESSION_VARIABLE信息USE_OF_DDL_STATEMENT信息USE_OF_WITH_HINT_CLAUSE信息OUTPUT_LENGTH_VIOLATION一致性  页面大小:10显示 1–10 条,共 16 条12 UNNECESSARY_VARIABLE

    对于每个变量,都会测试它是否由过程的任何输出参数使用,或者是否影响过程的结果。与结果相关的语句可以是 DML 语句、隐式结果集、控制语句条件。

    UNUSED_VARIABLE_VALUE

    如果赋给变量的值未在任何其它语句中使用,则可以删除赋值。如果是 DECLARE 语句中的缺省分配,则永远不会使用缺省值。

    UNCHECKED_SQL_INJECTION_SAFETY

    如果在动态 SQL 中使用 SQL 注入安全,应始终检查字符串类型的参数。此规则检查是否为该类型的每个参数调用函数 is_sql_injection_safe。

    对于 IF is_sql_injection_safe(:var) = 0 THEN... 之类的简单条件语句,检查真分支中的控制流。该过程应结束(通过返回或通过抛出错误),或者应使用函数 escape_single_quotes 或 escape_double_quotes 转义不安全的参数值,具体取决于值的使用位置。

    如果条件更复杂(例如,在一个条件中检查多个变量),将显示警告,因为只能检查动态 SQL 的任何执行是否通过 SQL 注入检查。

    SINGLE_SPACE_LITERAL

    此规则搜索仅包含一个空格的字符串 aterals。如果使用 ABAP VARCHAR MODE,则将此类字符串文字视为空字符串。在这种情况下,可使用 CHAR(32) 代替 ' '。

    COMMIT_OR_ROLLBACK_IN_DYNAMIC_SQL

    此规则检测使用 COMMITROLLBACK 语句的动态 SQL。建议直接在 SQLScript 中使用 COMMITROLLBACK,从而消除动态 SQL 的需求。

    此规则在分析动态 SQL 方面存在一些限制:

  • 它只能检查使用常量字符串的动态 SQL(例如 EXEC 'COMMIT';)。它无法检测计算任何表达式的动态 SQL(例如 EXEC 'COM' || 'MIT';)

  • 它只能检测包含 COMMITROLLBACK 和空格的简单字符串以及简单注释。此规则可能不会检测到更复杂的字符串。

    USE_OF_SELECT_IN_SCALAR_UDF

    此规则检测并报告标量 UDF 中的 SELECT 语句。标量 UDF 中的 SELECT 语句会影响性能。如果确实需要表操作,则应改用过程或表 UDF

    示例代码USE_OF_SELECT_IN SCALAR_UDFDO BEGIN tab = SELECT RULE_NAMESPACE, RULE_NAME, category FROM SQLSCRIPT_ANALYZER_RULES where rule_name = 'USE_OF_SELECT_IN_SCALAR_UDF'; CALL ANALYZE_SQLSCRIPT_DEFINITION(' CREATE FUNCTION f1(a INT) RETURNS b INT AS BEGIN DECLARE x INT; SELECT count(*) into x FROM _sys_repo.active_object; IF :a > :x THEN SELECT count(*) INTO b FROM _sys_repo.inactive_object; ELSE b = 100; END IF; END;', :tab, res); SELECT * FROM :res;END; 在此示例中,将报告以下查找结果: RULE_NAMESPACERULE_NAME类别SHORT_DESCRIPTIONSTART_POSITIONEND_POSITIONSAPUSE_OF_SELECT_IN_SCALAR_UDF性能在标量 UDF 中找到 SELECT 语句186240SAPUSE_OF_SELECT_IN_SCALAR_UDF性能在标量 UDF 中找到 SELECT 语句97149  USE_OF_UNASSIGNED_SCALAR_VARIABLE

    该规则检测已使用但从未明确分配的变量。这些变量在使用时仍具有默认值,可能未定义。建议分配一个默认值(可以是 NULL),以确保从变量中读取时获得所需的值。如果此规则返回警告或错误,请检查代码中是否没有为错误的变量赋值。更改代码后始终重新运行此规则,因为可能多个错误仅触发一条消息,并且错误仍然存在。

    对于每个 DECLARE 语句,此规则返回以下内容之一:

  • <无>:如果变量始终在使用前分配或未使用。所有内容均正确。

  • 变量 <变量> 可以取消分配:如果至少有一个分支,其中使用时未分配变量,即使已在其他分支中分配该变量。

  • 使用变量 <变量>,但从未显式分配:如果变量在使用时永远不会分配值。

    DML_STATEMENTS_IN_LOOPS

    该规则在循环中检测以下 DML 语句 - INSERTUPDATEDELETE、REPLACE/UPSERT。有时,可以重写循环并使用单个 DML 语句来提高性能。

    在以下示例中,表在 循环中更新。可以重写此代码以使用单个 DML 语句更新表。

    示例代码循环中的 DML 语句DO BEGINtab = select rule_namespace, rule_name, category from sqlscript_analyzer_rules;call analyze_sqlscript_definition(' Create procedure example() ASBEGIN declare i int = 0; declare size int; declare olda int; declare newa int; CREATE TABLE T1 (a INT); INSERT INTO T1 VALUES(1); INSERT INTO T1 VALUES(-2); INSERT INTO T1 VALUES(-1); INSERT INTO T1 VALUES(3); T2 = SELECT * FROM T1; SELECT COUNT(*) INTO size FROM T1; FOR i IN 1 .. :size DO olda = :T2.A[:i]; newa = :olda; if :olda < 0 then newa = 0; end if; UPDATE T1 SET A= :newa WHERE A = :olda; END FOR; SELECT * FROM T1;END; ', :tab, res);select * from :res;end; // Optimized version drop procedure example2;Create procedure example2() ASBEGIN declare i int = 0; declare size int; declare olda int; declare newa int; CREATE TABLE T1 (a INT); INSERT INTO T1 VALUES(1); INSERT INTO T1 VALUES(-2); INSERT INTO T1 VALUES(-1); INSERT INTO T1 VALUES(3); UPDATE T1 SET A = 0 WHERE A < 0; SELECT * FROM T1;END; DROP TABLE T1;CALL EXAMPLE2(); USE_OF_CE_FUNCTIONS

    该规则检查是否使用计算引擎计划运算符(CE 函数)。由于它们使优化更加困难并导致性能问题,因此应避免这种情况。有关详细信息以及如何仅使用纯 SQL 替换它们,请参阅计算引擎计划运算符

    USE_OF_DYNAMIC_SQL

    规则检查并报告是否在过程或函数中使用动态 SQL。

    ROW_COUNT_AFTER_SELECT

    该规则检查系统变量 ::ROWCOUNT 是否在 SELECT 语句之后使用。

    ROW_COUNT_AFTER_DYNAMIC_SQL

    该规则检查在使用动态 SQL 后是否使用系统变量 ::ROWCOUNT。

    USE_OF_SET_SESSION_VARIABLE

    此规则标识管理会话变量的 SQL 语句。会话变量用于存储可在整个会话期间访问的信息,从而允许自定义会话行为。常见用例包括设置时区、字符集或其他影响执行环境或查询结果的会话特定设置。正确管理会话变量可以增强应用程序功能和用户体验。

    在提供的示例中,应用 'USE_OF_SET_SESSION_VARIABLE' 规则来识别和管理代码中的会话变量。以下生成的输出表汇总了规则的查找结果,详细说明设置和取消设置会话变量的特定位置。

    DO BEGIN
    

    tab = SELECT rule_namespace, rule_name, category FROM SQLSCRIPT_ANALYZER_RULES;

    call sys.analyze_sqlscript_definition('

    do begin

    set ''session_var'' = ''hana'';

    unset ''session_var'';

    end;

    ', :tab, res);

    select * from :res;

    END;

    RULE_NAMESPACERULE_NAME类别SHORT_DESCRIPTIONSTART_POSITIONEND_POSITIONSAPUSE_OF_SET_SESSION_VARIABLE信息找到会话变量管理:session_var1339SAPUSE_OF_SET_SESSION_VARIABLE信息找到会话变量管理:session_var4463  USE_OF_DDL_STATEMENT

    此规则检测 SQL 脚本中的数据定义语言 (DDL) 语句。DDL 语句涉及定义、修改或删除数据库模式、对象(如表和索引)的命令,从而影响数据库数据的结构。识别这些语句有助于了解数据库中的模式更改和数据管理策略。

    DO BEGIN
    

    tab = SELECT rule_namespace, rule_name, category FROM SQLSCRIPT_ANALYZER_RULES;

    call sys.analyze_sqlscript_definition('

    create procedure proc1() as begin

    create table ta(a int);

    insert into ta values (1);

    drop table tab;

    end;

    ', :tab, res);

    select * from :res;

    END;

    RULE_NAMESPACERULE_NAME类别SHORT_DESCRIPTIONSTART_POSITIONEND_POSITIONSAPUSE_OF_DDL_STATEMENT信息找到了 DDL 语句的使用92106SAPUSE_OF_DDL_STATEMENT信息找到了 DDL 语句的使用3759  USE_OF_WITH_HINT_CLAUSE

    此规则旨在检测 SQL 查询中的 "WITH HINT" 子句。提示是 SQL 中指导数据库引擎如何执行查询(可能覆盖缺省查询执行计划)的特殊说明。其中包括有关索引使用、连接策略或其它优化提示的建议。虽然提示可以提高性能,但需要仔细考虑,因为如果数据库的数据分配随时间变化,它们可能会导致不太理想的执行计划。

    在提供的示例中,SQLScript 分析器应用 USE_OF_WITH_HINT_CLAUSE 规则来检查 SQL 过程中 "WITH HINT" 子句的使用。下表说明了过程中检测到的提示,标记它们在脚本中的位置。

    DO BEGIN
    

    tab = SELECT rule_namespace, rule_name, category FROM SQLSCRIPT_ANALYZER_RULES;

    call sys.analyze_sqlscript_definition('

    create procedure proc1(out o table(col nvarchar(10))) as begin

    o = select * from tudf1() with hint(workload_class("WLC"), no_inline);

    end;

    ', :tab, res);

    select * from :res;

    END;

    RULE_NAMESPACERULE_NAME类别SHORT_DESCRIPTIONSTART_POSITIONEND_POSITIONSAPUSE_OF_WITH_HINT_CLAUSE信息no_inline126135SAPUSE_OF_WITH_HINT_CLAUSE信息workload_class("WLC1")102124  OUTPUT_LENGTH_VIOLATION

    此规则执行静态分析,以检测过程及其嵌套过程中的输出长度违规。识别分配到输出表列的数据超出定义的列长度(这可能导致执行期间出现错误)的情况。

    为避免此类错误,请确保正确定义了过程或表用户定义函数 (UDF) 的输出表类型或返回表类型。如有必要,调整列长度以适应分配的数据。此规则还评估输入中指定的嵌套过程和表 UDF,以识别潜在问题。

    结果按以下格式显示:

    "".""."".""."": Target type  / Assigned type 

    示例:

    以下示例演示了 OUTPUT_LENGTH_VIOLATION 规则如何识别已分配数据长度与嵌套过程和函数设置中定义的列长度之间的潜在不匹配。

    CREATE SCHEMA codeanalyzer;
    

    SET SCHEMA codeanalyzer;

    CREATE TABLE tab(col NVARCHAR(5));

    INSERT INTO tab VALUES ('abcde');

    CREATE PROCEDURE inner_proc(OUT ot1 TABLE(col NVARCHAR(4))) AS

    BEGIN

    ot1 = SELECT * FROM tab;

    END;

    CREATE FUNCTION inner_func

    RETURNS TABLE(col NVARCHAR(4)) AS

    BEGIN

    RETURN SELECT * FROM tab;

    END;

    DO BEGIN

    tab = SELECT rule_namespace, rule_name, category FROM SQLSCRIPT_ANALYZER_RULES;

    CALL sys.analyze_sqlscript_definition('

    CREATE OR REPLACE PROCEDURE outer_proc(OUT ot2 TABLE(col NVARCHAR(3))) AS

    BEGIN

    CALL inner_proc(ot2);

    END;', :tab, res);

    SELECT * FROM :res;

    END;

    在此示例中:

  • tab 表定义长度为 5 的列。

  • inner_proc 过程定义长度为 4 的输出列 (ot1)。

  • outer_proc 过程调用 inner_proc 并定义长度为 3 的输出列 (ot2)。

    该规则标记每个级别定义的长度与实际分配数据长度之间的不匹配。预期输出显示在下表中。

    命名空间规则名称类别描述开始位置结束位置SAPOUTPUT_LENGTH_VIOLATION一致性“WM0”。”CODEANALYZER”。”INNER_PROC"."."OT1”。COL”:目标类型 NVARCHAR(4)/已分配类型 NVARCHAR(5)。8696  下一个示例演示当程序将函数的输出分配给其输出表时 OUTPUT_LENGTH_VIOLATION 规则如何标识长度违规:

    DO BEGIN
    

    tab = SELECT rule_namespace, rule_name, category FROM SQLSCRIPT_ANALYZER_RULES;

    call sys.analyze_sqlscript_definition('

    create procedure outer_proc(out ot2 table(col nvarchar(3))) as

    begin

    ot2 = select * from inner_func();

    end;', :tab, res);

    select * from :res;

    END;

    在此示例中,出现此问题的原因是:

  • inner_func 函数返回一个表,其中列长度定义为 NVARCHAR(4)。

  • outer_proc 过程输出列长度小于 ot2 的表 (NVARCHAR(3))。

  • 在将函数的 表分配给过程的输出表时, 规则会检测到此不一致以防止因截断数据而导致的错误。

    下表显示预期输出。

    命名空间规则名称类别描述开始位置结束位置内部 警告常规警告:表变量 "OT2" 的类型不匹配:属性 "COL" 的目标类型 "NVARCHAR(3)" 与分配类型 "NVARCHAR(4)" 不同:行 4 列 1(位于位置 73)。70 SAPOUTPUT_LENGTH_VIOLATION一致性“WM0”。”CODEANALYZER”。”OUTER_PROC"."."OT2”。COL”:目标类型 NVARCHAR(3)/已分配类型 NVARCHAR(4)。3359SAPOUTPUT_LENGTH_VIOLATION一致性“WM0”。”CODEANALYZER”。”INNER_FUNC"."."(FUNCTION RETURN TABLE)"."COL”:目标类型 NVARCHAR(4)/已分配类型 NVARCHAR(5)。90100 

    示例

    示例代码DO BEGIN tab = SELECT rule_namespace, rule_name, category FROM SQLSCRIPT_ANALYZER_RULES; -- selects all rules CALL ANALYZE_SQLSCRIPT_DEFINITION(' CREATE PROCEDURE UNCHECKED_DYNAMIC_SQL(IN query NVARCHAR(500)) AS BEGIN DECLARE query2 NVARCHAR(500) = ''SELECT '' || query || '' from tab''; EXEC :query2; query2 = :query2; --unused variable value END', :tab, res); SELECT * FROM :res;END; 示例代码DO BEGIN tab = SELECT rule_namespace, rule_name, category FROM SQLSCRIPT_ANALYZER_RULES; to_scan = SELECT schema_name, procedure_name object_name, definition FROM sys.procedures WHERE procedure_type = 'SQLSCRIPT2' AND schema_name IN('MY_SCHEMA','OTHER_SCHEMA') ORDER BY procedure_name; CALL analyze_sqlscript_objects(:to_scan, :tab, objects, findings); SELECT t1.schema_name, t1.object_name, t2.*, t1.object_definition FROM :findings t2 JOIN :objects t1 ON t1.object_definition_id = t2.object_definition_id;END;

    手动规则压缩

    由于静态代码分析的性质,SQLScript 代码分析器可能会产生误报。为避免在分析具有许多查找结果且可能存在许多误报的大型过程时出现混淆,代码分析器提供了一种手动禁止这些误报的方法。

    您可以使用 SQLScript 编译器定义应禁止的规则。编译指示指令名称为 AnalyzerSuppress,并且必须至少有一个参数描述应禁止哪个规则。

    示例代码create procedure proc as begin @AnalyzerSuppress('SAP.UNNECESSARY_VARIABLE.CONSISTENCY') declare a int;end