# 高レベルクエリビルダー（Supabaseライク）

DatabaseClientは、Supabaseライクな直感的なAPIでksqlDBとの連携を可能にする高レベルクエリビルダーです。

## 特徴

- 🔗 **メソッドチェーン** - 直感的なAPIデザイン
- 🛡️ **型安全性** - TypeScriptによる型チェック
- 🎯 **シンプル** - 複雑なSQL文を知らなくても使用可能
- 🔄 **自動変換** - ストリーム/テーブル名の自動変換
- ⚡ **最適化** - 効率的なクエリ生成

## セットアップ

```typescript
import { DatabaseClient, createDatabaseClient } from '@gftdcojp/gftd-ksqldb-orm/client';

// クライアントの作成
const dbClient = createDatabaseClient({
  ksql: {
    url: 'http://localhost:8088',
    apiKey: 'your-api-key',
    apiSecret: 'your-api-secret'
  },
  schemaRegistry: {
    url: 'http://localhost:8081'
  }
});

await dbClient.initialize();
```

## データ取得（SELECT）

### 基本的な使用法

```typescript
// 全てのレコードを取得
const { data, error } = await dbClient
  .from('users')
  .select('*')
  .execute();

// 特定のフィールドのみ選択
const { data } = await dbClient
  .from('products')
  .select('id, name, price')
  .execute();

console.log(data); // 結果の配列
```

### WHERE条件

#### 基本的な条件

```typescript
// 等価条件
const { data } = await dbClient
  .from('users')
  .eq('status', 'active')
  .execute();

// 複数条件（AND）
const { data } = await dbClient
  .from('orders')
  .eq('user_id', 123)
  .eq('status', 'completed')
  .execute();
```

#### 比較演算子

```typescript
// 大なり・小なり
const { data } = await dbClient
  .from('products')
  .gt('price', 100)        // price > 100
  .lt('stock', 50)         // stock < 50
  .execute();

// 以上・以下
const { data } = await dbClient
  .from('orders')
  .gte('created_at', '2024-01-01')  // created_at >= '2024-01-01'
  .lte('total_amount', 1000)        // total_amount <= 1000
  .execute();
```

#### パターンマッチング

```typescript
// LIKE検索
const { data } = await dbClient
  .from('users')
  .like('email', '%@example.com')
  .execute();

// 複数値検索（IN）
const { data } = await dbClient
  .from('products')
  .in('category_id', [1, 2, 3])
  .execute();
```

### ソートとページネーション

```typescript
// ソート（昇順）
const { data } = await dbClient
  .from('users')
  .order('created_at', true)   // ASC
  .execute();

// ソート（降順）
const { data } = await dbClient
  .from('products')
  .order('price', false)       // DESC
  .execute();

// ページネーション
const { data } = await dbClient
  .from('products')
  .limit(10)
  .offset(20)
  .execute();

// 組み合わせ
const { data } = await dbClient
  .from('orders')
  .eq('status', 'completed')
  .order('created_at', false)
  .limit(25)
  .offset(50)
  .execute();
```

### 単一レコード取得

```typescript
// 最初の1件を取得
const { data: user, error } = await dbClient
  .from('users')
  .eq('id', 123)
  .single();

if (error) {
  console.error('Error:', error);
} else if (user) {
  console.log('User found:', user);
} else {
  console.log('User not found');
}
```

## データ操作（INSERT/UPDATE/DELETE）

### データ挿入

```typescript
// 単一レコード挿入
const { data, error } = await dbClient
  .from('users')
  .insert({
    name: 'John Doe',
    email: 'john@example.com',
    status: 'active',
    created_at: new Date().toISOString()
  });

if (error) {
  console.error('Insert failed:', error);
} else {
  console.log('Inserted data:', data);
}
```

### データ更新

```typescript
// 条件に一致するレコードを更新
const { data, error } = await dbClient
  .from('users')
  .eq('id', 123)
  .update({
    email: 'newemail@example.com',
    updated_at: new Date().toISOString()
  });

// 複数条件での更新
const { data } = await dbClient
  .from('products')
  .eq('category_id', 1)
  .lt('stock', 10)
  .update({
    status: 'low_stock',
    updated_at: new Date().toISOString()
  });
```

### データ削除

```typescript
// 条件に一致するレコードを削除
const { data, error } = await dbClient
  .from('users')
  .eq('status', 'inactive')
  .delete();

// 特定IDのレコードを削除
const { data } = await dbClient
  .from('temp_data')
  .eq('id', 456)
  .delete();
```

## 型安全性

TypeScript型を使用することで、コンパイル時の型チェックが可能です。

```typescript
// 型定義
interface User {
  id: number;
  name: string;
  email: string;
  status: 'active' | 'inactive';
  created_at: string;
}

// 型安全なクエリ
const { data } = await dbClient
  .from<User>('users')
  .eq('status', 'active')  // 'active' | 'inactive' のみ受け入れ
  .execute();

// data は User[] 型として推論される
data.forEach(user => {
  console.log(user.name);    // 型安全
  console.log(user.invalid); // コンパイルエラー
});
```

## エラーハンドリング

```typescript
// 基本的なエラーハンドリング
const { data, error } = await dbClient
  .from('users')
  .eq('id', 123)
  .execute();

if (error) {
  console.error('Query failed:', error.message);
  console.error('Details:', error.details);
  console.error('Table:', error.tableName);
  console.error('Query type:', error.queryType);
  return;
}

// データを安全に使用
console.log('Users found:', data.length);
```

## 高度な使用例

### 複雑な検索クエリ

```typescript
// eコマースの商品検索
const searchProducts = async (filters: {
  category?: number[];
  priceMin?: number;
  priceMax?: number;
  inStock?: boolean;
  searchTerm?: string;
}) => {
  let query = dbClient.from('products').select('*');

  if (filters.category?.length) {
    query = query.in('category_id', filters.category);
  }

  if (filters.priceMin !== undefined) {
    query = query.gte('price', filters.priceMin);
  }

  if (filters.priceMax !== undefined) {
    query = query.lte('price', filters.priceMax);
  }

  if (filters.inStock) {
    query = query.gt('stock', 0);
  }

  if (filters.searchTerm) {
    query = query.like('name', `%${filters.searchTerm}%`);
  }

  return await query
    .order('name', true)
    .limit(50)
    .execute();
};

// 使用例
const { data: products } = await searchProducts({
  category: [1, 2],
  priceMin: 10,
  priceMax: 100,
  inStock: true,
  searchTerm: 'laptop'
});
```

### ページネーション実装

```typescript
const getPaginatedUsers = async (page: number, pageSize: number = 20) => {
  const offset = (page - 1) * pageSize;
  
  const { data, error } = await dbClient
    .from('users')
    .eq('status', 'active')
    .order('created_at', false)
    .limit(pageSize)
    .offset(offset)
    .execute();

  return {
    data,
    error,
    pagination: {
      page,
      pageSize,
      offset
    }
  };
};

// 使用例
const result = await getPaginatedUsers(2, 25); // 2ページ目、25件ずつ
```

### バッチ操作

```typescript
// 複数のデータ操作を順番に実行
const batchOperations = async () => {
  try {
    // 1. 古いデータを削除
    await dbClient
      .from('temp_logs')
      .lt('created_at', '2024-01-01')
      .delete();

    // 2. ステータスを更新
    await dbClient
      .from('users')
      .eq('last_login', null)
      .update({ status: 'inactive' });

    // 3. 新しいデータを挿入
    await dbClient
      .from('system_events')
      .insert({
        event_type: 'batch_cleanup',
        created_at: new Date().toISOString()
      });

    console.log('Batch operations completed successfully');
  } catch (error) {
    console.error('Batch operations failed:', error);
  }
};
```

## ベストプラクティス

### 1. 型定義の活用

```typescript
// interfaces/User.ts
export interface User {
  id: number;
  name: string;
  email: string;
  status: 'active' | 'inactive';
  created_at: string;
  updated_at?: string;
}

// services/UserService.ts
import { User } from '../interfaces/User';

export class UserService {
  constructor(private dbClient: DatabaseClient) {}

  async getActiveUsers(): Promise<User[]> {
    const { data, error } = await this.dbClient
      .from<User>('users')
      .eq('status', 'active')
      .execute();

    if (error) throw new Error(`Failed to get users: ${error.message}`);
    return data;
  }

  async updateUserEmail(userId: number, newEmail: string): Promise<void> {
    const { error } = await this.dbClient
      .from<User>('users')
      .eq('id', userId)
      .update({
        email: newEmail,
        updated_at: new Date().toISOString()
      });

    if (error) throw new Error(`Failed to update user: ${error.message}`);
  }
}
```

### 2. エラーハンドリングの統一

```typescript
// utils/DatabaseUtils.ts
export const executeWithErrorHandling = async <T>(
  operation: () => Promise<{ data: T[]; error?: any }>
): Promise<T[]> => {
  try {
    const { data, error } = await operation();
    
    if (error) {
      console.error('Database operation failed:', error);
      throw new Error(error.message || 'Unknown database error');
    }
    
    return data;
  } catch (error) {
    console.error('Database operation exception:', error);
    throw error;
  }
};

// 使用例
const users = await executeWithErrorHandling(() =>
  dbClient.from('users').eq('status', 'active').execute()
);
```

### 3. クエリの再利用

```typescript
// queries/UserQueries.ts
export class UserQueries {
  constructor(private dbClient: DatabaseClient) {}

  // 基本クエリ
  private baseUserQuery() {
    return this.dbClient
      .from<User>('users')
      .select('id, name, email, status, created_at');
  }

  // アクティブユーザー取得
  getActiveUsers(limit?: number) {
    let query = this.baseUserQuery().eq('status', 'active');
    
    if (limit) {
      query = query.limit(limit);
    }
    
    return query.execute();
  }

  // ユーザー検索
  searchUsers(searchTerm: string) {
    return this.baseUserQuery()
      .like('name', `%${searchTerm}%`)
      .order('name', true)
      .execute();
  }

  // 最近登録されたユーザー
  getRecentUsers(days: number = 7) {
    const cutoffDate = new Date();
    cutoffDate.setDate(cutoffDate.getDate() - days);
    
    return this.baseUserQuery()
      .gte('created_at', cutoffDate.toISOString())
      .order('created_at', false)
      .execute();
  }
}
```

## テーブル名の自動変換

DatabaseClientは、ksqlDBのストリーム/テーブル命名規則に自動で対応します。

```typescript
// 'users' を指定すると...
await dbClient.from('users').execute();
// 実際には 'users_table' に対してクエリが実行される

// 'users_stream' を指定すると...
await dbClient.from('users_stream').execute();
// 'users_table' に自動変換されてクエリが実行される

// 既に '_table' サフィックスがある場合
await dbClient.from('users_table').execute();
// そのまま 'users_table' に対してクエリが実行される
```

## 接続確認とヘルスチェック

```typescript
// データベース接続の確認
const healthCheck = async () => {
  const result = await dbClient.health();
  
  if (result.status === 'ok') {
    console.log('Database connection is healthy');
  } else {
    console.error('Database connection failed:', result.details);
  }
  
  return result;
};

// アプリケーション起動時に実行
await healthCheck();
```

## 制限事項

### 1. ksqlDBの制約
- JOIN操作は直接サポートされていません（低レベルAPIを使用）
- 複雑な集約関数は使用できません
- ウィンドウ関数は使用できません

### 2. ブラウザ環境での制限
- ファイル操作は利用できません
- 一部のNode.js専用機能は使用できません

### 3. プルクエリの制限
- リアルタイムデータには対応していません（プッシュクエリを使用）
- 大量データの取得には適していません

## トラブルシューティング

### よくあるエラー

```typescript
// 1. テーブルが見つからない
// Error: Table 'unknown_table' not found
// → テーブル名を確認、またはスキーマを作成

// 2. 型の不一致
// Error: Cannot convert value to expected type
// → フィールドの型を確認

// 3. 権限エラー  
// Error: Access denied
// → RLSポリシーまたはAPI認証を確認
```

### デバッグ方法

```typescript
// 生成されるクエリを確認（内部メソッド）
const query = dbClient.from('users').eq('status', 'active');
console.log('Generated query:', query.buildSelectQuery());

// エラーの詳細情報を取得
const { error } = await dbClient.from('users').execute();
if (error) {
  console.log('Error details:', {
    message: error.message,
    tableName: error.tableName,
    queryType: error.queryType,
    details: error.details
  });
}
``` 