PostgreSQL「syntax error in tsquery」の原因と対処
- 作成日 2025.09.11
- 更新日 2025.10.06
- PostgreSQL
- PostgreSQL
全文検索の tsquery 構文が不正なときに出るエラー。未対応の記号を含む文字列を to_tsquery に渡す、演算子の並び/括弧が不正、引用符の使い方違い、ストップワード除去で式が崩れる、プレフィックス指定 :* の誤用、ダブルクオートの取り違えなどが主因。安全なクエリ生成は「ユーザー入力→plainto_tsquery/websearch_to_tsquery」を原則にし、to_tsquery を使うときは自前で無害化・検証する。
- 1. エラーの意味と発生条件(tsquery 構文の基本)
- 2. 最短再現(よくある NG 例)
- 3. to_tsquery / plainto_tsquery / phraseto_tsquery / websearch_to_tsquery の違い
- 4. ユーザー入力から安全にクエリ化(原則テンプレ)
- 5. 予約記号の無害化(to_tsquery を使う場合の前処理)
- 6. フレーズ検索・前方一致・ブール演算の正しい書き方
- 7. 括弧と演算子の優先順位(崩れると即エラー)
- 8. ストップワードと空クエリの扱い(落ちない形に整える)
- 9. 実務テンプレ:安全ラッパー関数(空なら NULL)
- 10. デバッグ:どこで壊れているかを特定(ts_debug/::text)
- 11. インデックスと検索の基本形(GIN/GiST + tsvector)
- 12. エラーを起こす典型パターン→書き換え(NG→OK 早見)
- 13. 入力正規化のクイックレシピ(Bash/SQL)
- 14. エラーを握り潰さず運用で防ぐ(アプリ側の要点)
- 15. テスト観点(回帰しやすい境界ケース)
エラーの意味と発生条件(tsquery 構文の基本)
tsquery は AND(&) / OR(|) / NOT(!) / 位置演算子(<->) / 括弧() / プレフィックス(:*) を持つブール式。to_tsquery は「すでに構文化された式」を受けるため、未エスケープの記号や不正な並びを含むと「syntax error in tsquery」になる。plainto_tsquery / phraseto_tsquery / websearch_to_tsquery は「自然文→式」を安全に生成するため原則こちらを使う。
最短再現(よくある NG 例)
-- 1) 連続演算子:左辺/右辺がない
SELECT to_tsquery('english', 'cat & & dog'); -- NG: syntax error in tsquery
-- 2) ダブルクオートを to_tsquery に渡す(これは websearch 用の記法)
SELECT to_tsquery('english', '"cat dog"'); -- NG
-- 3) C++ などの記号語をそのまま
SELECT to_tsquery('english', 'c++'); -- NG
-- 4) プレフィックス :* の付け方が不正
SELECT to_tsquery('english', 'postgres:*'); -- 失敗する例あり(書き方注意)
-- 5) 括弧の不一致
SELECT to_tsquery('simple', '(cat | dog'); -- NGto_tsquery / plainto_tsquery / phraseto_tsquery / websearch_to_tsquery の違い
to_tsquery は「構文済み」。演算子/括弧/プレフィックスを自分で正しく書けるときに使う。plainto_tsquery は空白区切りの単語列を AND で連結して安全に変換。phraseto_tsquery は「語句を隣接(<->)で連結」。websearch_to_tsquery は Google 風(引用符でフレーズ、-で除外、OR など)を安全に tsquery に変換。ユーザー入力には websearch_to_tsquery、少なくとも plainto_tsquery を優先。
ユーザー入力から安全にクエリ化(原則テンプレ)
-- もっとも安全:web 検索風の入力をそのまま受ける
SELECT websearch_to_tsquery('english', ' "cat dog" OR rabbit -mouse ');
-- 単語列なら plainto(AND 連結)
SELECT plainto_tsquery('english', 'cat dog rabbit');
-- 明示フレーズなら phraseto
SELECT phraseto_tsquery('english', 'cat dog');予約記号の無害化(to_tsquery を使う場合の前処理)
-- 予約記号を空白へ、連続空白を 1 つに
-- & | ! ( ) : < > ' " は to_tsquery で問題になりやすい
SELECT regexp_replace(
regexp_replace($$C++ (intro) & tips$$, '[&|!():<>''"]', ' ', 'g'),
'\s+', ' ', 'g');
-- C++ のような記号語は plainto/websearch で吸収するのが無難
SELECT websearch_to_tsquery('english', '"c++"'); -- OKフレーズ検索・前方一致・ブール演算の正しい書き方
-- フレーズ(to_tsquery を使うなら <-> で連結)
SELECT to_tsquery('english', 'cat<->dog'); -- 「cat」と「dog」が隣接
-- 前方一致(lexeme':* の形。lexeme は基本クオートする)
SELECT to_tsquery('simple', '''postgre'':*'); -- 'postgre':*
-- OR / AND / NOT
SELECT to_tsquery('english', 'cat | dog & !mouse');括弧と演算子の優先順位(崩れると即エラー)
& は AND、| は OR、! は単項 NOT。! は直後にオペランドが必要で、& や | を続けると不正。括弧は必ず対応させる。ストップワードが消えた結果「演算子の片側が消える」ことがあるため、to_tsquery では特に注意。
ストップワードと空クエリの扱い(落ちない形に整える)
英語の the, a, an などは除去される。to_tsquery に「the &」のような文字列を渡すと、the が消えた結果で構文が壊れてエラーになりやすい。plainto_tsquery/websearch_to_tsquery は自動で安全な式に落とすが、to_tsquery で自前構築するなら「空・片側欠落」を検出して NULL(条件なし)にする分岐を用意する。
実務テンプレ:安全ラッパー関数(空なら NULL)
-- ユーザー入力を websearch→(失敗時)plain にフォールバックし、空は NULL
CREATE OR REPLACE FUNCTION safe_tsquery(conf regconfig, q text)
RETURNS tsquery
LANGUAGE plpgsql IMMUTABLE AS $$
DECLARE t tsquery;
BEGIN
IF q IS NULL OR btrim(q) = '' THEN
RETURN NULL;
END IF;
BEGIN
t := websearch_to_tsquery(conf, q);
EXCEPTION WHEN syntax_error THEN
t := plainto_tsquery(conf, q);
END;
IF t::text = '' THEN
RETURN NULL;
END IF;
RETURN t;
END$$;
-- 利用例
SELECT to_tsvector('english', title || ' ' || body) @@ safe_tsquery('english', :q);
デバッグ:どこで壊れているかを特定(ts_debug/::text)
-- トークナイズ/辞書適用の結果を確認
SELECT * FROM ts_debug('english', 'C++ tips for the cat and the dog');
-- tsquery の文字列表現を確認(演算子の付き方を可視化)
SELECT to_tsquery('english', 'cat & dog | !mouse')::text;インデックスと検索の基本形(GIN/GiST + tsvector)
-- 1) tsvector を作って GIN インデックス
CREATE INDEX idx_docs_fts
ON docs USING GIN (to_tsvector('english', coalesce(title,'') || ' ' || coalesce(body,'')));
-- 2) 検索は @@ 演算子で
SELECT id, title
FROM docs
WHERE to_tsvector('english', coalesce(title,'') || ' ' || coalesce(body,'')) @@
websearch_to_tsquery('english', :q)
ORDER BY ts_rank_cd(to_tsvector('english', title || ' ' || body),
websearch_to_tsquery('english', :q)) DESC
LIMIT 50;エラーを起こす典型パターン→書き換え(NG→OK 早見)
-- NG: ユーザー入力をそのまま to_tsquery
SELECT to_tsquery('english', :q); -- ❌
-- OK: websearch_to_tsquery / plainto_tsquery
SELECT websearch_to_tsquery('english', :q); -- ✅
-- NG: "ダブルクオート" を to_tsquery に渡す
SELECT to_tsquery('english', '"cat dog"'); -- ❌
-- OK: フレーズは phraseto_tsquery か websearch_to_tsquery
SELECT phraseto_tsquery('english', 'cat dog'); -- ✅
-- NG: プレフィックスの書式
SELECT to_tsquery('simple', 'postgres:*'); -- ❌(lexeme の指定が不正)
-- OK: lexeme':* 形式(シングルクオート)
SELECT to_tsquery('simple', '''postgres'':*'); -- ✅ 'postgres':*
入力正規化のクイックレシピ(Bash/SQL)
-- Bash: ユーザー入力らしき文字列から危険記号を空白化して渡す(最終手段)
echo "$q" | sed 's/[&|!():"'\'']\+//g' | tr -s ' ' | psql -v q -c \
"SELECT websearch_to_tsquery('english', :'q');"
-- SQL: 記号を取り除いた上で plainto に渡す(安全優先)
WITH cleaned AS (
SELECT regexp_replace(:q, '[&|!():"'\'']', ' ', 'g') AS s
)
SELECT plainto_tsquery('english', s) FROM cleaned;エラーを握り潰さず運用で防ぐ(アプリ側の要点)
ユーザー入力→そのまま to_tsquery を禁止。websearch_to_tsquery をデフォルトにし、失敗や空(tsquery::text = ”)は条件をスキップするか 400 を返す。検索ボックスに「フレーズは引用符、除外は -、OR は大文字」などの簡易ヘルプを併記して無効な文字の混入を抑える。
テスト観点(回帰しやすい境界ケース)
C++/C#、メールアドレス、URL、括弧/記号だらけの文字列、ダブルクオート含み、片括弧、末尾が演算子、ストップワードだけ、全角記号混在、日本語と英数字の境界。これらを websearch_to_tsquery と plainto_tsquery の双方で通し、意図通りの tsquery になるかを確認する。
-
前の記事
PostgreSQLでのエラー『 table referenced in 』の原因と対処 2025.09.10
-
次の記事
PostgreSQL「ERROR: column must appear in the GROUP BY clause」の原因と対処 2025.09.12
コメントを書く