# Durable Object SQLite 조회 가이드 Durable Object의 내장 SQLite 데이터베이스를 조회하는 여러 가지 방법을 제공합니다. ## 📋 목차 1. [웹 인터페이스로 조회](#1-웹-인터페이스로-조회) 2. [API 엔드포인트로 조회](#2-api-엔드포인트로-조회) 3. [WebSocket으로 조회](#3-websocket으로-조회) 4. [사용 가능한 테이블](#4-사용-가능한-테이블) --- ## 1. 웹 인터페이스로 조회 가장 간편한 방법입니다. 브라우저에서 바로 SQLite 데이터를 확인할 수 있습니다. ### 접속 방법 ``` http://localhost:8788/sql-viewer ``` ### 기능 - **📊 DB 정보**: 데이터베이스 크기, 테이블 목록, 레코드 수 등 - **👥 사용자 목록**: 모든 사용자 조회 - **🎲 배팅 목록**: 최근 배팅 내역 조회 - **⚙️ 커스텀 쿼리**: 원하는 SQL 쿼리 직접 실행 ### 예시 화면 ``` ┌─────────────────────────────────────┐ │ 🔍 Durable Object SQLite Viewer │ ├─────────────────────────────────────┤ │ [📊 DB 정보] [👥 사용자] [🎲 배팅] │ │ │ │ 결과: │ │ { │ │ "databaseSize": 12345, │ │ "userCount": 10, │ │ "betCount": 50 │ │ } │ └─────────────────────────────────────┘ ``` --- ## 2. API 엔드포인트로 조회 프로그래밍 방식으로 데이터를 조회할 수 있습니다. ### 2.1 데이터베이스 정보 조회 ```bash # HTTP GET curl http://localhost:8788/sql-api/info ``` **응답 예시:** ```json { "databaseSize": 24576, "userCount": 3, "betCount": 15, "currentGameId": "550e8400-e29b-41d4-a716-446655440000", "tables": [ { "name": "user" }, { "name": "current_bet" } ] } ``` ### 2.2 모든 사용자 조회 ```bash curl http://localhost:8788/sql-api/users ``` **응답 예시:** ```json [ { "id": "user-123", "nickname": "홍길동", "email": "hong@example.com", "joinGameCount": 5, "capital": 15000 } ] ``` ### 2.3 배팅 내역 조회 ```bash # 모든 배팅 (최근 100개) curl http://localhost:8788/sql-api/bets # 특정 게임의 배팅 curl "http://localhost:8788/sql-api/bets?gameId=550e8400-e29b-41d4-a716-446655440000" ``` **응답 예시:** ```json [ { "id": 1, "gameId": "550e8400-e29b-41d4-a716-446655440000", "diceNum": 12, "userId": "user-123", "betType": "odd", "amount": 1000, "isWin": 1, "reward": 2000 } ] ``` ### 2.4 커스텀 SQL 쿼리 실행 ```bash curl -X POST http://localhost:8788/sql-api/query \ -H "Content-Type: application/json" \ -d '{"query": "SELECT * FROM user WHERE capital > 10000"}' ``` --- ## 3. WebSocket으로 조회 실시간 게임 중에 WebSocket 연결을 통해 SQL을 조회할 수 있습니다. ### JavaScript 예시 ```javascript // WebSocket 연결 const ws = new WebSocket('ws://localhost:8788/api/counter'); // DB 정보 조회 ws.send(JSON.stringify({ type: 'sqlQuery', query: 'info' })); // 모든 사용자 조회 ws.send(JSON.stringify({ type: 'sqlQuery', query: 'users' })); // 커스텀 쿼리 ws.send(JSON.stringify({ type: 'sqlQuery', query: 'SELECT * FROM current_bet WHERE amount > 5000' })); // 결과 수신 ws.onmessage = (event) => { const data = JSON.parse(event.data); if (data.type === 'sqlResult') { console.log('SQL 결과:', data.data); } else if (data.type === 'sqlError') { console.error('SQL 에러:', data.error); } }; ``` --- ## 4. 사용 가능한 테이블 ### 4.1 `user` 테이블 사용자 정보를 저장합니다. | 컬럼 | 타입 | 설명 | |------|------|------| | `id` | TEXT | 사용자 고유 ID (PRIMARY KEY) | | `nickname` | TEXT | 닉네임 | | `email` | TEXT | 이메일 (UNIQUE) | | `joinGameCount` | INTEGER | 참여한 게임 횟수 | | `capital` | INTEGER | 현재 자본금 | **예제 쿼리:** ```sql -- 모든 사용자 조회 SELECT * FROM user; -- 자본금이 10000 이상인 사용자 SELECT * FROM user WHERE capital >= 10000; -- 가장 많이 참여한 사용자 SELECT * FROM user ORDER BY joinGameCount DESC LIMIT 10; ``` ### 4.2 `current_bet` 테이블 배팅 정보를 저장합니다. | 컬럼 | 타입 | 설명 | |------|------|------| | `id` | INTEGER | 배팅 고유 ID (PRIMARY KEY, AUTO INCREMENT) | | `gameId` | TEXT | 게임 고유 ID | | `diceNum` | INTEGER | 주사위 합계 | | `userId` | TEXT | 사용자 ID (FOREIGN KEY) | | `betType` | TEXT | 배팅 타입 (odd/even/big/small) | | `amount` | INTEGER | 배팅 금액 | | `isWin` | INTEGER | 승리 여부 (0=패배, 1=승리) | | `reward` | INTEGER | 보상 금액 | **예제 쿼리:** ```sql -- 모든 배팅 조회 SELECT * FROM current_bet; -- 특정 게임의 배팅 SELECT * FROM current_bet WHERE gameId = 'xxx'; -- 사용자별 총 배팅액 SELECT userId, SUM(amount) as totalBet FROM current_bet GROUP BY userId; -- 승리한 배팅만 조회 SELECT * FROM current_bet WHERE isWin = 1; -- 게임별 통계 SELECT gameId, COUNT(*) as betCount, SUM(amount) as totalAmount, SUM(CASE WHEN isWin = 1 THEN 1 ELSE 0 END) as winCount FROM current_bet GROUP BY gameId; ``` --- ## 5. 고급 쿼리 예제 ### 5.1 사용자별 승률 계산 ```sql SELECT u.nickname, COUNT(cb.id) as totalBets, SUM(CASE WHEN cb.isWin = 1 THEN 1 ELSE 0 END) as wins, CAST(SUM(CASE WHEN cb.isWin = 1 THEN 1 ELSE 0 END) AS FLOAT) / COUNT(cb.id) * 100 as winRate FROM user u LEFT JOIN current_bet cb ON u.id = cb.userId GROUP BY u.id, u.nickname HAVING COUNT(cb.id) > 0; ``` ### 5.2 배팅 타입별 통계 ```sql SELECT betType, COUNT(*) as count, SUM(amount) as totalAmount, AVG(amount) as avgAmount, SUM(CASE WHEN isWin = 1 THEN 1 ELSE 0 END) as winCount FROM current_bet GROUP BY betType; ``` ### 5.3 최근 게임 분석 ```sql SELECT gameId, diceNum, COUNT(*) as betCount, SUM(amount) as totalBet, SUM(reward) as totalReward FROM current_bet WHERE gameId = (SELECT gameId FROM current_bet ORDER BY id DESC LIMIT 1) GROUP BY gameId, diceNum; ``` --- ## 6. 주의사항 ### 보안 - **프로덕션 환경**: `/sql/query` 엔드포인트는 개발용입니다. 프로덕션에서는 제거하거나 인증을 추가하세요. - **SQL Injection**: 사용자 입력을 직접 쿼리에 넣지 마세요. 항상 파라미터 바인딩을 사용하세요. ### 성능 - **인덱스 활용**: `gameId`와 `userId`에 인덱스가 설정되어 있습니다. - **LIMIT 사용**: 큰 테이블 조회 시 항상 `LIMIT`를 사용하세요. ### 비용 - Cloudflare Durable Objects의 SQLite 사용 시 [요금 정책](https://developers.cloudflare.com/durable-objects/platform/pricing/#sqlite-storage-backend)을 확인하세요. - Row read/write에 따라 비용이 발생합니다. --- ## 7. 문제 해결 ### "COUNTER binding not found" 에러 - `wrangler.jsonc`에 Durable Object 바인딩이 올바르게 설정되어 있는지 확인하세요. ### 빈 결과가 반환됨 - 아직 사용자나 배팅이 생성되지 않았을 수 있습니다. - 게임에 접속하여 배팅을 해보세요. ### WebSocket 연결 실패 - 개발 서버가 실행 중인지 확인하세요: `pnpm run dev` --- ## 8. 실전 예제 ### 게임 시작 시 사용자 생성 ```javascript // counter-do.ts의 fetch 메서드에서 this.createOrUpdateUser( session.id, 'Player1', 'player1@example.com', 10000 ); ``` ### 배팅 저장 ```javascript // startNoMoreBetPeriod 메서드에서 주사위를 굴린 후 const diceSum = this.dice1! + this.dice2! + this.dice3!; this.sessions.forEach((session) => { if (session.oddBet > 0) { this.saveBet({ gameId: this.gameId!, diceNum: diceSum, userId: session.id, betType: 'odd', amount: session.oddBet, isWin: diceSum % 2 === 1 ? 1 : 0, reward: diceSum % 2 === 1 ? session.oddBet * 2 : 0 }); } }); ``` --- 완성! 🎉 이제 Durable Object의 SQLite 데이터베이스를 다양한 방법으로 조회할 수 있습니다.