如何在Python 3.x中使用pyparsing从Oracle SQL脚本中删除注释?

huangapple go评论58阅读模式
英文:

How do I remove comments from an Oracle SQL script using pyparsing in Python 3.x?

问题

我理解你的问题,你想要解决的是从包含多个SQL语句的Oracle SQL脚本中删除注释,并将脚本拆分成独立的语句。你已经使用了pyparsing库来处理单行注释和多行注释,但遇到了一些问题。以下是你可能想要考虑的一些建议:

  1. 解决多行注释问题:
    run_tests方法似乎不适用于多行注释。你可以考虑使用parseString方法来测试多行注释,这将允许你更好地控制输入字符串和检查解析的结果。

  2. 处理注释和SQL标记的重叠问题:
    当注释与SQL标记重叠时,你可以考虑按顺序处理它们,先检查注释,然后再检查SQL标记。例如,可以首先使用单行注释和多行注释的语法来处理注释,然后再尝试解析SQL标记。这样,你可以确保注释不会被错误地解释为SQL标记。

  3. 处理有效字符中的 "-" 问题:
    你提到 "-" 字符可能会导致问题。为了解决这个问题,你可以在合适的地方使用转义字符来处理 "-",以确保它不会被错误地解释为注释或语句的一部分。

  4. 结构化代码:
    对于复杂的问题,良好的代码结构非常重要。你可以将不同的功能模块化,例如一个模块处理注释,另一个模块处理SQL标记。这将使你的代码更易于理解和维护。

最后,确保在处理Oracle SQL脚本时考虑各种边缘情况,以确保你的解决方案具有足够的鲁棒性。如果你需要更多的帮助或示例代码,请随时提出具体的问题。

英文:

I have an oracle sql script containing multiple statements which are separated by ";". I'm trying to remove all comments within this entire script. This includes:

  1. single line comments -- till a newline.
  2. block comments /* ... */
  3. Additionally, I would like to be able to split the entire script into its statements.

I know of the library called sqlparse that can format to remove code, but I came across this edge case:

edge_case = """myval := oneval || otherval ||--;
-- single comment line
lpad(nvl(myval, ' '), 10, ' ');
"""

formatted = sqlparse.format(edge_case, strip_comments=True)
sqlparse.split(formatted)

# returns ['myval := oneval || otherval ||--;', "lpad(nvl(myval, ' '), 10, ' ');"]
# it splits into 2 statements when it should only be 1!

My idea is to focus on each commenting case, and then try to parse the rest of non-commented code by just string tokens which consists of all valid characters in sql.

from pyparsing import *

# define basic elements
semicolon = Literal(";")
# all valid chars excluding semicolon
all_valid_chars = alphanums + "_-+*/.,:()[]{}<>=&|"

# define an sql token
sql_token = Word(all_valid_chars)


# need to make sure that "--" is a special case
single_line_comment_token = Literal("--")
single_line_comment = single_line_comment_token + SkipTo(line_end) + line_end
test1_single_line_comment = """
-- single comment line
--- also a comment
---- also a comment
-- comment -- continues
-- comment /* also continues */

# - not a comment
"""
single_line_comment.run_tests(test1_single_line_comment)


# also handle multi line comments
multi_line_comment_open = Literal("/*")
multi_line_comment_close = Literal("*/")
multi_line_comment = multi_line_comment_open + SkipTo(multi_line_comment_close) + multi_line_comment_close

test1_multi_line_comment = """
/* multi line comment */
/* outer comment /* inner comment */

/* /* /* still valid */
/* -- still valid */
"""
multi_line_comment.run_tests(test1_multi_line_comment)

test2_multi_line_comment = """
/* multi line comment /* with nested comment should fail! */ */
"""
multi_line_comment.run_tests(test2_multi_line_comment, failure_tests=True)

I'm now stuck here and not sure where to continue.

statement2 = OneOrMore(single_line_comment | multi_line_comment | sql_token) + semicolon

# run tests
print(statement2.parseString("myval := oneval || otherval ||--;"))

My issues:

  1. The tests for each of these cases work, but I dont know how I should be testing the together to cover overlaps eg. -- /* this should be a single line comment etc.
  2. I don't know how to handle the rest of the non commented code properly. For example, all valid characters should include -, but this should mess up my single line code?
    Less technical issues faced:
  3. Honestly not sure where to even begin or how to structure my code since these cases can overlap. I'm not even sure if pyparsing is what I should be doing to solve this problem, but based on questions i've seen trying to remove comments from sql, parsing is the only real robust solution.
  4. run_tests does not work with multiple lines. This makes it hard to test the block comment although i can use parse_string.

答案1

得分: 1

用于去除注释(或任何pyparsing表达式)的方法是使用transform_string,而不是parse_stringtransform_string会扫描输入字符串并应用解析动作和抑制操作。以下是从脚本中删除Python注释的一些代码示例:

import pyparsing as pp

comment = "#" + pp.rest_of_line
# 也可以使用提供的 `python_style_comment`
# comment = pp.python_style_comment

python_source = """
def hello(s):
    # say a nice hello!
    print(f"Hi, {s}!")
    # add a hashtag for social media
    print("#hello")
"""

# 抑制注释并转换字符串
print(comment.suppress().transform_string(python_source))

输出结果:

def hello(s):
    
    print(f"Hi, {s}!")
    
    print("

糟糕,这并没有检测到#hello在引号字符串内。

为了修复这个问题,我们还需要插入一个解析引号字符串的解析器,但这些我们不会抑制:

# 首先解析引号字符串,以防它们包含注释
transform_expr = pp.quoted_string | comment.suppress()
print(transform_expr.transform_string(python_source))

现在输出结果为:

def hello(s):
    
    print(f"Hi, {s}!")
    
    print("#hello")

对于处理SQL注释,您可以做类似的操作:

sql_single_line_comment = '--' + pp.rest_of_line

# 使用pyparsing的C样式 /* ... */ 多行注释定义
sql_multi_line_comment = pp.c_style_comment

comment_remover = (
    # 将引号字符串解析为单独的部分,以避免被抑制
    pp.quoted_string
    | (sql_single_line_comment | sql_multi_line_comment).suppress()
)

sql = "SELECT * FROM STUDENTS; -- watch out for Bobby Tables!"
print(comment_remover.transform_string(sql))

输出结果:

SELECT * FROM STUDENTS;

我很高兴看到您正在使用run_tests!如果您想定义跨多行的测试,而不是使用多行字符串形式,请将测试作为字符串列表传递:

expr.run_tests([test_str1, test_str2, test_str3])

不幸的是,我没有一个调用transform_stringrun_tests版本。

将行拆分为单独的语句最好在第二次处理后进行,首先去除注释。您可以尝试类似以下的方法:

semi = pp.Literal(";")
semi.add_parse_action(pp.replace_with(";\n\n"))

然后使用与去除注释相同的转换模式(但这次使用解析动作而不是抑制)。或者使用scan_string来定位分号";"终止符,然后将位于分号之间的SQL写入单独的文件(留作读者的练习)。

英文:

For stripping comments (or any pyparsing expression), you should use transform_string, not parse_string. transform_string scans through the input string and applies parse actions and suppressions. Here is some code to strip out python comments from a script:

import pyparsing as pp

comment = "#" + pp.rest_of_line
# could also use the provided `python_style_comment`
# comment = pp.python_style_comment

python_source = """
def hello(s):
    # say a nice hello!
    print(f"Hi, {s}!")
    # add a hashtag for social media
    print("#hello")
"""

# suppress comments and transform the string
print(comment.suppress().transform_string(python_source))

giving:

def hello(s):
    
    print(f"Hi, {s}!")
    
    print("

Oops, this does not detect the fact that #hello is inside a quoted string.

To fix this, we also insert a parser for quoted strings, but these we don't suppress:

# parse quoted strings first, in case they contain a comment
transform_expr = pp.quoted_string | comment.suppress()
print(transform_expr.transform_string(python_source))

Now giving:

def hello(s):
    
    print(f"Hi, {s}!")
    
    print("#hello")

For your SQL comment handling, you'll do much the same:

sql_single_line_comment = '--' + pp.rest_of_line

# use pyparsing's definition for C-style /* ... */ multiline comments
sql_multi_line_comment = pp.c_style_comment

comment_remover = (
    # parse quoted strings separately so that they don't get suppressed
    pp.quoted_string
    | (sql_single_line_comment | sql_multi_line_comment).suppress()
)

sql = "SELECT * FROM STUDENTS; -- watch out for Bobby Tables!"
print(comment_remover.transform_string(sql))

prints:

SELECT * FROM STUDENTS;

I'm glad to see you are using run_tests! If you want to define tests that span multiple lines, than instead of using the multiline string form, pass the test as a list of strings:

expr.run_tests([test_str1, test_str2, test_str3])

Unfortunately, I don't have a version of run_tests that calls transform_string.

Splitting the lines up into separate statements is best done in a second pass, after first stripping out the comments. You might try something like:

semi = pp.Literal(";")
semi.add_parse_action(pp.replace_with(";\n\n")

And then use the same transformation pattern as was used to strip comments (but this time, using the parse action instead of suppressing). Or use scan_string to locate the ";" terminators, and then write out the SQL that lies between semicolons out to their separate files (left as an exercise for the reader).

huangapple
  • 本文由 发表于 2023年6月2日 00:12:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/76383840.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定