Skip to content
This repository was archived by the owner on Jun 30, 2025. It is now read-only.

Commit 273294e

Browse files
tunamaguroclaude
andcommitted
docs: add comprehensive struct-based API design document
Add detailed design specification for struct-based API with typed-builder pattern: - Zero-cost type-state pattern for compile-time safety - Integration with existing copy_types optimization - QueryAnnotation-based execution methods - Backward compatibility strategy - Implementation roadmap and risk analysis 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent b6e2fa7 commit 273294e

File tree

1 file changed

+380
-0
lines changed

1 file changed

+380
-0
lines changed

docs/struct-based-api.md

Lines changed: 380 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,380 @@
1+
# Struct-Based API Design and Implementation
2+
3+
## 概要
4+
5+
本文書では、sqlc-rust-postgresプラグインにおける構造体ベースAPI(Struct-Based API)の設計、実装戦略、および技術的判断の根拠について詳述する。この機能は、現在の関数ベースAPIの課題を解決しつつ、ゼロコスト抽象化を実現する新しいAPIパターンを提供する。
6+
7+
## 背景と問題意識
8+
9+
### 現在の課題
10+
11+
現在のsqlc-rust-postgresプラグインが生成するAPIには、以下の重要な問題がある:
12+
13+
#### 1. 関数引数の肥大化問題
14+
15+
SQLクエリのパラメータが増加すると、生成される関数の引数が過度に多くなる:
16+
17+
```rust
18+
// 問題のある例:引数が多すぎて可読性・保守性が低い
19+
pub async fn update_user_profile(
20+
client: &Client,
21+
user_id: i64,
22+
name: &str,
23+
email: &str,
24+
phone: &str,
25+
address: &str,
26+
birth_date: Option<chrono::NaiveDate>,
27+
is_verified: bool,
28+
updated_at: chrono::DateTime<chrono::Utc>,
29+
) -> Result<UpdateUserProfileRow, Error>
30+
```
31+
32+
#### 2. パラメータ順序の誤用リスク
33+
34+
引数の順序は型システムでは検証されないため、SQLクエリの変更により引数順序が変わっても、型が一致していればコンパイルエラーにならない:
35+
36+
```rust
37+
// SQLクエリ変更前:WHERE user_id = $1 AND status = $2
38+
get_user_by_status(client, user_id, status).await?;
39+
40+
// SQLクエリ変更後:WHERE status = $1 AND user_id = $2
41+
// ↓ 引数順序が入れ替わったが、型は同じなのでコンパイルエラーにならない
42+
get_user_by_status(client, user_id, status).await?; // バグ!
43+
```
44+
45+
#### 3. 型安全性の欠如
46+
47+
関数の引数リストでは、どの値がどのSQLパラメータに対応するかが位置に依存するため、リファクタリング時の安全性が低い。
48+
49+
### 解決すべき要求仕様
50+
51+
上記課題を踏まえ、以下の要求仕様を満たすAPIを設計する:
52+
53+
1. **型安全性の向上**: パラメータの設定漏れや重複設定をコンパイル時に検出
54+
2. **可読性の向上**: どの値がどのパラメータに対応するかが明確
55+
3. **保守性の向上**: SQLクエリの変更に対してより堅牢
56+
4. **パフォーマンス**: ゼロコスト抽象化の実現
57+
5. **後方互換性**: 既存APIを破壊しない
58+
59+
## 設計アプローチ
60+
61+
### 基本設計思想
62+
63+
本実装では **型状態パターン(Type State Pattern)** を採用し、以下の原則に基づいて設計する:
64+
65+
#### 1. コンパイル時安全性の最大化
66+
67+
Rustの型システムを活用し、実行時エラーを可能な限りコンパイル時エラーに変換する:
68+
69+
```rust
70+
// コンパイル時に全パラメータの設定を強制
71+
let query = GetUser::builder()
72+
.user_id(123) // 必須パラメータ
73+
.include_deleted(false) // 必須パラメータ
74+
.build(); // 全パラメータ設定済みの場合のみコンパイル成功
75+
```
76+
77+
#### 2. ゼロコスト抽象化の実現
78+
79+
builderパターンの実装において、実行時コストを排除する:
80+
81+
- `Option<T>`を使わずに型状態でフィールドの設定状態を管理
82+
- `unwrap()`等の実行時チェックを排除
83+
- 最終的な`build()`メソッドは単純なメモリ操作のみ
84+
85+
#### 3. copy_types最適化の統合
86+
87+
既存の`copy_types`設定と連携し、パラメータの渡し方を最適化する:
88+
89+
- Copy型(i32, bool等): 値渡しで効率化
90+
- 非Copy型(String等): `Cow<'a, T>`で借用/所有を適応的に選択
91+
92+
## 実装戦略
93+
94+
### 1. 型状態パターンの実装
95+
96+
#### 基本構造
97+
98+
```rust
99+
// クエリパラメータを保持する最終構造体
100+
pub struct GetUser<'a> {
101+
user_id: i32, // Copy型:値渡し
102+
name: Cow<'a, str>, // 非Copy型:Cowで最適化
103+
}
104+
105+
// 型状態を管理するbuilder
106+
pub struct GetUserBuilder<'a, Fields = ((), ())> {
107+
fields: Fields, // 実際の値を型レベルで管理
108+
phantom: PhantomData<&'a ()>, // ライフタイム保持
109+
}
110+
```
111+
112+
#### 段階的型状態の変化
113+
114+
```rust
115+
// 初期状態:未設定
116+
GetUserBuilder<'a, ((), ())>
117+
118+
// user_id設定後:第1フィールドのみ設定済み
119+
GetUserBuilder<'a, (i32, ())>
120+
121+
// name設定後:全フィールド設定済み
122+
GetUserBuilder<'a, (i32, Cow<'a, str>)>
123+
```
124+
125+
#### ゼロコストbuilder実装
126+
127+
```rust
128+
impl<'a, V2> GetUserBuilder<'a, ((), V2)> {
129+
pub fn user_id(self, user_id: i32) -> GetUserBuilder<'a, (i32, V2)> {
130+
let ((), name) = self.fields;
131+
GetUserBuilder {
132+
fields: (user_id, name), // 単純な値の移動のみ
133+
phantom: PhantomData,
134+
}
135+
}
136+
}
137+
138+
// build()メソッド:unwrap()なしの単純なdestructuring
139+
impl<'a> GetUserBuilder<'a, (i32, Cow<'a, str>)> {
140+
pub fn build(self) -> GetUser<'a> {
141+
let (user_id, name) = self.fields; // ゼロコスト
142+
GetUser { user_id, name }
143+
}
144+
}
145+
```
146+
147+
### 2. QueryAnnotationに基づく実行メソッド
148+
149+
SQLCのクエリアノテーションに基づいて、適切な実行メソッドを生成する:
150+
151+
```rust
152+
impl<'a> GetUser<'a> {
153+
// :one アノテーション → query_one & query_opt
154+
pub async fn query_one(&self, client: &Client) -> Result<UserRow, Error>
155+
pub async fn query_opt(&self, client: &Client) -> Result<Option<UserRow>, Error>
156+
157+
// :many アノテーション → query_many & query_raw
158+
pub async fn query_many(&self, client: &Client) -> Result<Vec<UserRow>, Error>
159+
pub async fn query_raw(&self, client: &Client) -> Result<impl Iterator<Item=Result<UserRow, Error>>, Error>
160+
161+
// :exec アノテーション → execute
162+
pub async fn execute(&self, client: &Client) -> Result<u64, Error>
163+
}
164+
```
165+
166+
### 3. copy_types最適化の統合
167+
168+
既存の`copy_types`設定と統合し、型ごとに最適なパラメータ渡しを実現する:
169+
170+
#### 自動判定ルール
171+
172+
1. **Copy trait実装型かつ16バイト以下**: 値渡し
173+
2. **String**: `Cow<'a, str>`で最適化
174+
3. **ユーザー定義copy_types**: 設定に従って値渡し
175+
4. **その他**: 参照渡し
176+
177+
#### 実装例
178+
179+
```rust
180+
// 生成される構造体(copy_types最適化適用後)
181+
pub struct CreateUser<'a> {
182+
id: i32, // Copy型:値渡し
183+
name: Cow<'a, str>, // 文字列:Cowで最適化
184+
email: Cow<'a, str>, // 文字列:Cowで最適化
185+
is_active: bool, // Copy型:値渡し
186+
metadata: &'a serde_json::Value, // 大きな型:参照渡し
187+
}
188+
```
189+
190+
## 後方互換性戦略
191+
192+
### 既存APIの保持
193+
194+
新しい構造体ベースAPIは**追加機能**として実装し、既存の関数ベースAPIを破壊しない:
195+
196+
```rust
197+
// 既存API:そのまま維持(内部実装のみ最適化)
198+
pub async fn get_user(client: &Client, user_id: i32, name: &str) -> Result<UserRow, Error> {
199+
// 内部でGetUser構造体を使用(外部には露出しない)
200+
let query = GetUser {
201+
user_id,
202+
name: Cow::Borrowed(name),
203+
};
204+
query.query_one(client).await
205+
}
206+
207+
// 新しいAPI:オプションとして提供
208+
let user = GetUser::builder()
209+
.user_id(123)
210+
.name("test")
211+
.build()
212+
.query_one(&client)
213+
.await?;
214+
```
215+
216+
### 段階的移行戦略
217+
218+
1. **フェーズ1**: 構造体ベースAPIの実装と検証
219+
2. **フェーズ2**: ドキュメント整備とサンプル提供
220+
3. **フェーズ3**: ユーザーフィードバックに基づく改善
221+
4. **フェーズ4**: 既存APIの非推奨化(長期計画)
222+
223+
## 実装の技術的詳細
224+
225+
### 1. コード生成フロー
226+
227+
```
228+
SQL解析 → パラメータ抽出 → copy_types適用 → 構造体生成 → builder生成 → 実行メソッド生成
229+
```
230+
231+
#### 主要な生成フェーズ
232+
233+
1. **パラメータ分析**: SQLクエリからパラメータを抽出
234+
2. **型最適化**: copy_types設定に基づく型変換
235+
3. **構造体生成**: 最適化された型でクエリ構造体を生成
236+
4. **builder生成**: 型状態パターンでbuilderを生成
237+
5. **メソッド生成**: QueryAnnotationに基づく実行メソッド生成
238+
239+
### 2. 実装上の制約と対処
240+
241+
#### 制約1: Rustの型システムの限界
242+
243+
**問題**: タプルの要素数には限界があるため、極端に多いパラメータを持つクエリでは型状態パターンが適用できない。
244+
245+
**対処**: パラメータ数が閾値(例:16個)を超える場合は、従来の`Option<T>`ベースのbuilderにフォールバック。
246+
247+
#### 制約2: ライフタイム管理の複雑さ
248+
249+
**問題**: 複数の借用パラメータが存在する場合、ライフタイム管理が複雑になる。
250+
251+
**対処**:
252+
- `PhantomData<&'a ()>`でライフタイムを統一
253+
- 必要に応じて複数ライフタイムパラメータを使用
254+
- `Cow<'a, T>`で借用と所有を適応的に切り替え
255+
256+
### 3. パフォーマンス考慮事項
257+
258+
#### ゼロコスト抽象化の検証
259+
260+
以下の観点でパフォーマンスを検証する:
261+
262+
1. **コンパイル時最適化**: リリースビルドでbuilderパターンが完全に最適化されるか
263+
2. **メモリ使用量**: 構造体のメモリレイアウトが効率的か
264+
3. **実行時コスト**: 従来の関数型APIと比較して性能劣化がないか
265+
266+
#### 最適化指針
267+
268+
```rust
269+
// 最適化前(実行時コストあり)
270+
struct Builder {
271+
user_id: Option<i32>, // Option型によるオーバーヘッド
272+
name: Option<String>, // unwrap()による実行時チェック
273+
}
274+
275+
// 最適化後(ゼロコスト)
276+
struct Builder<Fields = ((), ())> {
277+
fields: Fields, // 型レベルでの状態管理
278+
phantom: PhantomData<()>, // コンパイル時のみ
279+
}
280+
```
281+
282+
## 設定オプションの拡張
283+
284+
### 既存設定の活用
285+
286+
構造体ベースAPIは既存の`copy_types`設定を活用し、新たな設定項目は追加しない方針とする。これにより設定の複雑化を避け、シンプルな利用体験を維持する。
287+
288+
```json
289+
{
290+
"plugins": [
291+
{
292+
"name": "rust-postgres",
293+
"options": {
294+
"copy_types": ["uuid::Uuid", "CustomId"],
295+
"existing_options": "..."
296+
}
297+
}
298+
]
299+
}
300+
```
301+
302+
## 今後の拡張計画
303+
304+
### 短期計画(3ヶ月以内)
305+
306+
1. **基本実装の完成**: 型状態パターンの実装
307+
2. **copy_types統合**: 既存最適化との連携
308+
3. **テストケース整備**: 包括的なテストスイート
309+
4. **ドキュメント整備**: API仕様とサンプルコード
310+
311+
### 中期計画(6ヶ月以内)
312+
313+
1. **エラーメッセージ改善**: コンパイルエラーの分かりやすさ向上
314+
2. **IDE統合**: rust-analyzerでの補完とエラー表示改善
315+
3. **パフォーマンス検証**: ベンチマークとプロファイリング
316+
4. **エコシステム対応**: 主要なPostgreSQLクレートとの互換性確認
317+
318+
### 長期計画(1年以内)
319+
320+
1. **バッチ操作対応**: BatchExec等のアノテーション対応
321+
2. **トランザクション統合**: 型安全なトランザクション管理
322+
3. **マイグレーション支援**: 既存コードの移行ツール
323+
4. **他データベース対応**: MySQL、SQLite等への展開
324+
325+
## リスク分析と対策
326+
327+
### 技術的リスク
328+
329+
#### リスク1: コンパイル時間の増加
330+
331+
**原因**: 複雑な型状態パターンによるコンパイル負荷増加
332+
333+
**対策**:
334+
- 型の複雑さを適切に制限
335+
- インクリメンタルコンパイルの最適化
336+
- 必要に応じてマクロによる実装簡素化
337+
338+
#### リスク2: エラーメッセージの複雑化
339+
340+
**原因**: 型状態パターンの型エラーが分かりにくい
341+
342+
**対策**:
343+
- 適切な型エイリアスの提供
344+
- カスタムコンパイルエラーメッセージ
345+
- 詳細なドキュメントと例示
346+
347+
### 採用リスク
348+
349+
#### リスク1: 学習コストの増加
350+
351+
**原因**: 新しいAPIパターンの習得が必要
352+
353+
**対策**:
354+
- 段階的な移行計画
355+
- 豊富なサンプルコードとチュートリアル
356+
- 既存APIとの併用期間の確保
357+
358+
#### リスク2: エコシステムの分断
359+
360+
**原因**: 新旧APIの並存による混乱
361+
362+
**対策**:
363+
- 明確な移行ガイドライン
364+
- ツールによる自動変換支援
365+
- コミュニティとの十分な対話
366+
367+
## 結論
368+
369+
構造体ベースAPIの導入により、以下の価値を提供できる:
370+
371+
1. **開発者体験の向上**: 型安全性と可読性の大幅な改善
372+
2. **保守性の向上**: SQLクエリ変更に対する堅牢性
373+
3. **パフォーマンス**: ゼロコスト抽象化による効率性
374+
4. **段階的移行**: 既存コードへの影響を最小化
375+
376+
本実装は、Rustの型システムの力を最大限に活用し、データベースアクセスレイヤーの安全性と効率性を大幅に向上させる重要な機能である。慎重な実装と十分な検証を通じて、sqlc-rust-postgresプラグインの価値を更に高めることができると確信している。
377+
378+
---
379+
380+
*本文書の内容は実装の進行に伴い更新される予定である。最新の情報については、GitHubのissueとPRを参照されたい。*

0 commit comments

Comments
 (0)