質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.34%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Q&A

解決済

1回答

190閲覧

pyparsingを用いて,特定パターン字句(=SQLクエリ構文の一部分文字列(但し複数句含む))をパースしたい。

171RR

総合スコア1

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

0グッド

0クリップ

投稿2025/01/05 12:45

実現したいこと

  1. ターゲット文字列(=ユーザー入力)に対して,予め定義済の特定パターン字句(但し複数句含む)を用いてパースする。
  2. 上記特定パターン字句を含むならば,パターン該当文字列を抜き出す。
  3. 上記特定パターン字句内の特定文字列と比較して,

  ターゲット文字列が非該当文字を含んでいれば,当該文字列をエラー対象としてエラー表示する。

※ 特定パターン字句内の特定文字列とは具体的に,
SQLデータベースのカラム名に該当する単なる文字列です。
ターゲット文字列内のカラム名相当文字列がこのデータベースカラム名文字列に完全合致するか否かを識別したいです。
※ ターゲット文字列は,特定パターン字句を複数句含む場合を想定しています。

発生している問題・分からないこと

下記のソースコード内に, コメントとして, 実行結果あるいは注釈を記述しています。

A. クエリ列文字列に対して,ターゲット文字列内のクエリ列文字列相当が完全合致していれば,正常処理出来ます。
B. クエリ列文字列に対して,ターゲット文字列内のクエリ列文字列相当が非合致分を含んでいると,
複数句全てを検知することが出来なくなり,そして,その原因となった文字をエラー表示することが出来ません。
→ この,検知出来た特定パターン字句のみを解析結果とするという挙動は,一見正しい挙動のようにも見えます。
ですが,エラーとなった特定パターン字句を明確化し,その原因となった文字をエラー表示したいとも思っています。

該当のソースコード

Python

1from pyparsing import * 2 3def define_grammer(num): # 解析構文は最終的に1つだが,質問の為に選択式に改変している。 4 # クエリ列候補 5 query_header = ["ID", "名前", "誕生日"] 6 クエリ列候補 = oneOf(query_header) 7 8 # 解析文字列 9 クエリ列文字列 = Suppress('"') + クエリ列候補 + Suppress('"') 10 LIKE = Keyword('LIKE') 11 検索文字列 = Combine(Suppress('"') + Word(alphas) + Suppress('"')) 12 AND = Keyword('AND') 13 # クエリ構文(1句) 14 クエリ構文1 = Group(クエリ列文字列 + LIKE + 検索文字列) 15 16 # 解析構文例 17 grammer = [] 18 grammer.append(クエリ構文1 + ZeroOrMore(AND)) # クエリ構文[0] ・・・ 1句 19 grammer.append(クエリ構文1 + ZeroOrMore(AND) + クエリ構文1 + ZeroOrMore(AND)) # クエリ構文[1] ・・・ 2句 20 grammer.append(ZeroOrMore(クエリ構文1 + ZeroOrMore(AND))) # クエリ構文[2] ・・・ ZeroOrMoreで複数句のつもり(★) 21 # クエリ構文が複数句,かつ,クエリ文字列の誤検出文字列を検知する(以下 "S"等)場合に於いて,★をどう記述したら良いかが分からない。 22 rgrammer = grammer[num] 23 return rgrammer 24 25def test_parse(grammer, target_string): 26 try: 27 parseresults = grammer.parse_string(target_string) 28 print(parseresults) 29 except ParseException as pe: 30 print(f"{pe.explain(depth=0)}") 31 print() 32 return 33 34 35if __name__ == '__main__': 36 # パーステスト (コメント文は,上記のParseException内容) 37 38 # ParseException: Expected ID | 名前 | 誕生日, found 'sID' (at char 1), (line:1, col:2) -> クエリ列文字列に非合致。検知出来ている。 39 grammer = define_grammer(0) 40 test_parse(grammer, '"SID"') 41 42 # ParseException: Expected '"', found 'S' (at char 3), (line:1, col:4) -> クエリ列文字列に非合致。誤検出文字列(S)を検知出来ている。(◎) 43 grammer = define_grammer(0) 44 test_parse(grammer, '"IDS"') 45 46 # [['ID', 'LIKE', 'a']] -> クエリ列文字列に合致。 47 grammer = define_grammer(0) 48 test_parse(grammer, '"ID" LIKE "a"') 49 50 # [['ID', 'LIKE', 'a'], 'AND', ['名前', 'LIKE', 'b']] -> クエリ列文字列に合致。 51 grammer = define_grammer(1) 52 test_parse(grammer, '"ID" LIKE "a" AND "名前" LIKE "b"') 53 54 # [['ID', 'LIKE', 'a'], 'AND', ['名前', 'LIKE', 'b']] -> クエリ列文字列に合致。 55 grammer = define_grammer(2) 56 test_parse(grammer, '"ID" LIKE "a" AND "名前" LIKE "b"') 57 58 # [['ID', 'LIKE', 'a'], 'AND'] -> クエリ列文字列2句目が欠落しており,さらに誤検知がされていない。ここで◎のように誤検知したい。 59 grammer = define_grammer(2) 60 test_parse(grammer, '"ID" LIKE "a" AND "名前S" LIKE "b"')

試したこと・調べたこと

  • teratailやGoogle等で検索した
  • ソースコードを自分なりに変更した
  • 知人に聞いた
  • その他
上記の詳細・結果

どうやら,
特定パターン句を複数句化(=ZeroOfMore/OneOfMore)した瞬間に,
oneOf(=本来は前方部分一致だが,
前後に文字列""を組み合わせることで完全合致判定のようなことが出来るようになっている模様)
が作用しなくなっているように思います。
特定パターン字句を明示的に回数分だけ記述すれば意図するように動作するようですが,
この複数回を簡潔に書ける方法を知りたいと思い,こちらに質問させて頂いています。

  • 特定パターン字句の再帰化? ・・・ 再帰関数で対応可能?? 私には分かりませんでした。
  • ソースコードの改変 ・・・ pyparsing oneOf関数について,完全合致の実現を試みましたが,上記ソースコードに落ち着きました。

補足

pyparsing 3.2.1

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

171RR

2025/01/05 12:58

sql構文を解析する為のPythonバインディングライブラリsqlparseでは, 上記のターゲット文字列が本物のSQL構文になっている必要がありそうです。 私が今検討しているのは,SQL構文の内,部分文字列をパースするというものです。 SELECTやFROM等の構文予約語は, 別のライブラリ(PyQt QSqlTableModel等)に任せており, 可変部分となる検索に関する文字列のみを入力したいという事情があります。
guest

回答1

0

ベストアンサー

まず、パターンについてですが、
クエリ構文1 + ZeroOrMore(AND) + クエリ構文1 + ZeroOrMore(AND)
だと、"ID" LIKE "a" AND "名前" LIKE "b" のほかに、AND を省略した "ID" LIKE "a" "名前" LIKE "b"とか、AND を複数挿入した "ID" LIKE "a" AND AND AND "名前" LIKE "b" AND AND みたいなものもOKになるのですが、これは希望の動作でしょうか?

やりたいのは
クエリ構文1 + ZeroOrMore(AND + クエリ構文1)
みたいな感じではないかと思ったのですが、どうでしょう。

→ この,検知出来た特定パターン字句のみを解析結果とするという挙動は,一見正しい挙動のようにも見えます。

こちらについては、parse_string()parse_all=True のオプションをつけることで文字列全体を解析するようになります。
ただし、やってみたらわかると思いますが、エラーとしては ParseException: Expected end of text, found 'AND' というように、文字列が終わるべきところで終わっていないというのが出るだけで、AND 以降のパターンのエラーの原因までは出てきません。ZeroOrMore を使うとこうなってしまうのは仕方ないと思います。
ZeroOrMore の中のエラーを拾うとしたら、クエリ構文1でParseExceptionが発生したらそこで解析を止まるようにすればできそうです。(set_fail_action()でParseFatalExceptionをraiseするようにする)

python

1def raise_fatal(s, loc, expr, err): 2 raise ParseFatalException(s, loc, str(err)) 3 4def define_grammer(_): 5 query_header = ["ID", "名前", "誕生日"] 6 クエリ列候補 = oneOf(query_header) 7 8 クエリ列文字列 = Suppress('"') + クエリ列候補 + Suppress('"') 9 LIKE = Keyword('LIKE') 10 検索文字列 = Combine(Suppress('"') + Word(alphas) + Suppress('"')) 11 AND = Keyword('AND') 12 クエリ構文1 = Group(クエリ列文字列 + LIKE + 検索文字列).set_fail_action(raise_fatal) 13 14 return クエリ構文1 + ZeroOrMore(AND + クエリ構文1) 15 16def test_parse(grammer, target_string): 17 try: 18 parseresults = grammer.parse_string(target_string, parse_all=True) 19 print(parseresults) 20 except ParseBaseException as pe: 21 print(f"{pe.explain(depth=0)}") 22 print()

投稿2025/01/08 07:01

bsdfan

総合スコア4843

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

171RR

2025/01/08 12:50

bsdfan様: ご回答誠にありがとうございます。 まずご回答頂けたこと自体が嬉しく,解決出来たことでさらに嬉しいです。 > AND を複数挿入した "ID" LIKE "a" AND AND AND "名前" LIKE "b" AND AND みたいなものもOKになるのですが、これは希望の動作でしょうか? ご指摘ありがとうございます。おっしゃるとおり,所望の動作ではないですね。 ご指摘頂けるまでの間に, AND/OR等の語句で一旦スプリットしてから,その上で各部分文字列をパースするか・・・とも考えたりしましたが,さらにややこしくなりそうで困り果てていました。 > やりたいのは ・・・(略)・・・どうでしょう。 まさにその通りです。意図を汲んで下さり,どうもありがとうございます。 > やってみたらわかると思いますが、・・・(略) ご教示ありがとうございます。ずっと考えていましたが,やはりそうなりますよね。。。 > ZeroOrMore の中のエラーを拾うとしたら、クエリ構文1でParseExceptionが発生したらそこで解析を止まるようにすればできそうです。(set_fail_action()でParseFatalExceptionをraiseするようにする) > クエリ構文1 + ZeroOrMore(AND + クエリ構文1) こちらが今回のキーポイントとなっており,非常に重要な部分です。重ね重ね,ありがとうございます。 この部分をご教示頂けたことで, エラー未検知のまま進める,あるいは,良く分からないエラー(")を出しつつそのまま進めるという事態を回避することが出来ました。 この度は,多大なるヒントをご教示頂き,誠にありがとうございました!!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.34%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問