# NanaSQLite

[![PyPI version](https://img.shields.io/pypi/v/nanasqlite.svg)](https://pypi.org/project/nanasqlite/)
[![Python versions](https://img.shields.io/pypi/pyversions/nanasqlite.svg)](https://pypi.org/project/nanasqlite/)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![Downloads](https://static.pepy.tech/badge/nanasqlite)](https://pepy.tech/project/nanasqlite)
[![Tests](https://github.com/disnana/nanasqlite/actions/workflows/ci.yml/badge.svg)](https://github.com/disnana/nanasqlite/actions/workflows/ci.yml)

**A dict-like SQLite wrapper with instant persistence and intelligent caching.**

[English](#english) | [日本語](#日本語)

---

## English

### 🚀 Features

- **Dict-like Interface**: Use familiar `db["key"] = value` syntax
- **Instant Persistence**: All writes are immediately saved to SQLite
- **Smart Caching**: Lazy load (on-access) or bulk load (all at once)
- **Nested Structures**: Full support for nested dicts and lists (up to 30+ levels)
- **High Performance**: WAL mode, mmap, and batch operations for maximum speed
- **Security & Stability (v1.2.0)**: SQL validation, ReDoS protection, and strict connection management
- **Zero Configuration**: Works out of the box with sensible defaults

### 📦 Installation

```bash
pip install nanasqlite
```

Optional installation extras:

```bash
# Performance boosters (orjson + lru-dict)
pip install "nanasqlite[speed]"

# Enable encryption features (AES-GCM/ChaCha20/Fernet)
pip install "nanasqlite[encryption]"

# Install all optional runtime features
pip install "nanasqlite[all]"

# Development tools (pytest, ruff, mypy, tox, etc.)
pip install -e ".[dev]"
```

### ⚡ Quick Start

```python
from nanasqlite import NanaSQLite

# Create or open a database
db = NanaSQLite("mydata.db")

# Use it like a dict
db["user"] = {"name": "Nana", "age": 20, "tags": ["admin", "active"]}
print(db["user"])  # {'name': 'Nana', 'age': 20, 'tags': ['admin', 'active']}

# Data persists automatically
db.close()

# Reopen later - data is still there!
db = NanaSQLite("mydata.db")
print(db["user"]["name"])  # 'Nana'
```

### 🔧 Advanced Usage

```python
# Bulk load for faster repeated access
db = NanaSQLite("mydata.db", bulk_load=True)

# Batch operations for high-speed reads/writes
db.batch_update({"k1": "v1", "k2": "v2"})
results = db.batch_get(["k1", "k2"])

# Context manager support
with NanaSQLite("mydata.db") as db:
    db["temp"] = "value"
```

### 📚 Documentation

- **[Official Documentation Website ↗](https://nanasqlite.disnana.com/)** (Best Experience)
- [English Guide](https://nanasqlite.disnana.com/en/guide)
- [API Reference (Sync)](https://nanasqlite.disnana.com/en/api_sync)
- [API Reference (Async)](https://nanasqlite.disnana.com/en/api_async)
- [Benchmark Trends 📊](https://nanasqlite.disnana.com/dev/bench/)
- [Migration Guide (v1.1.x to v1.2.0)](MIGRATION_GUIDE.md)

### ✨ v1.5.x New Features (Ultimate Hooks)

**Ultimate Hooks: Flexible Validation & External Constraints**
Intercept database operations with custom hooks or built-in constraints. Easily validate data, enforce uniqueness, check foreign keys, or flawlessly integrate with Pydantic.

```python
from nanasqlite import NanaSQLite
from nanasqlite.hooks import UniqueHook, CheckHook, PydanticHook
from pydantic import BaseModel

class UserConfig(BaseModel):
    version: int
    theme: str

db = NanaSQLite("config.db")
db.add_hook(UniqueHook("email"))
db.add_hook(CheckHook(lambda k, v: v.get("age", 0) >= 18, "Age must be >= 18"))
db.add_hook(PydanticHook(UserConfig))

# Write a dict, reads back as a Pydantic Model automatically!
db["user_conf"] = {"version": 1, "theme": "dark", "email": "test@example.com", "age": 20}
config = db["user_conf"]
print(config.theme)  # 'dark'
```

### ✨ v1.4.x New Features (v2 Architecture)
Introduced an optional "Write-Back Cache" architecture where all KVS writes are instantly available in memory but saved to SQLite asynchronously in the background. Read latency remains exactly zero.

```python
# Enable v2 mode with automatic background flushing
db = NanaSQLite("high_load.db", v2_mode=True, flush_mode="time", flush_interval=1.0)

# Optional: Use V2Config for cleaner initialization (v1.4.1+)
from nanasqlite import V2Config
cfg = V2Config(flush_mode="time", flush_interval=5.0, enable_metrics=True)
db = NanaSQLite("mydata.db", v2_mode=True, v2_config=cfg)

# The main thread is NEVER blocked by disk I/O!
db["heavy_key"] = validate_and_compute_data()
```

> ⚠️ **WARNING**: v2 mode is built for SINGLE-PROCESS systems. Do not use it with multi-worker setups (e.g., Gunicorn with multiple workers) as parallel background threads will corrupt the SQLite file.

### ✨ v1.3.x New Features

- **Advanced Cache Strategies**: LRU and TTL support. [Learn more](https://nanasqlite.disnana.com/en/guide#lesson-10-cache-strategies)
- **Data Encryption**: Secure storage with AES-GCM (default), ChaCha20, or Fernet. [Learn more](https://nanasqlite.disnana.com/en/guide#lesson-11-encryption)
- **Persistence TTL**: Self-expiring data for sessions and temporary storage.
- **Lock Timeout** (v1.3.4b1): Raise `NanaSQLiteLockError` if the lock is held too long, preventing indefinite hangs in multi-threaded apps.
- **Backup & Restore** (v1.3.4b1): Online backup via APSW's SQLite backup API and one-call restore from any backup file.
- **Security Audit & Hardening** (v1.3.4): Whitelist-based column type validation, AEAD nonce validation, closed-instance safety on all dict methods. See [CHANGELOG](CHANGELOG.md) for full details.
- **Security Audit & v2 Bug Fixes** (v1.4.0): Fixed SQL injection in `create_table()` column types, V2Engine callback ordering, async child attribute inheritance, and v2 mode read-query bypass. See [CHANGELOG](CHANGELOG.md) for full details.
- **Critical Bug Fix** (v1.4.1): Fixed `AttributeError` in `upsert()` / `aupsert()` when using specific call patterns with `conflict_columns`. Added negative caching for LRU/TTL to improve performance.
- **Refactoring & Quality** (v1.4.1): Introduced `V2Config` to group engine parameters and resolve SonarCloud warnings. Improved Docker CI robustness. See [CHANGELOG](CHANGELOG.md) for full details.

```python
# Lock Timeout
db = NanaSQLite("app.db", lock_timeout=2.0)

# Backup
db.backup("snapshot.db")

# Restore
db.restore("snapshot.db")
```

### ✨ v1.2.0 New Features

**Security Enhancements & Strict Connection Management:**

```python
# v1.2.0 Security Features
db = NanaSQLite("mydata.db", 
    strict_sql_validation=True,  # Disallow unauthorized SQL functions
    max_clause_length=500        # Limit SQL length to prevent ReDoS
)

# v1.2.0 Read-Only Connection Pool (Async only)
async with AsyncNanaSQLite("mydata.db", read_pool_size=5) as db:
    # Heavy read operations (query, fetch_all) use the pool automatically
    # allowing parallel execution without blocking writes or other reads
    results = await asyncio.gather(
        db.query("logs", where="level=?", parameters=("ERROR",)),
        db.query("logs", where="level=?", parameters=("INFO",)),
        db.query("logs", where="level=?", parameters=("WARN",))
    )

# Strict Connection Management
with db.transaction():
    sub_db = db.table("sub")
    # ... operations ...
db.close()
# Accessing sub_db now raises NanaSQLiteClosedError for safety!
```

**[Read Secure Development Guide ↗](https://nanasqlite.disnana.com/en/guide#_2-security-v1-2-0-)**

### ✨ v1.1.0 New Features

**Safely operate multiple tables in the same database with shared connections:**

```python
from nanasqlite import NanaSQLite

# Create main table instance
main_db = NanaSQLite("mydata.db", table="users")

# Get another table instance sharing the same connection
products_db = main_db.table("products")
orders_db = main_db.table("orders")

# Each table has isolated cache and operations
main_db["user1"] = {"name": "Alice", "email": "alice@example.com"}
products_db["prod1"] = {"name": "Laptop", "price": 999}
orders_db["order1"] = {"user": "user1", "product": "prod1"}
```


**Transaction Support & Error Handling (v1.1.0+):**

```python
from nanasqlite import NanaSQLite, NanaSQLiteTransactionError

with db.transaction():
    db["key1"] = "value1"
    db["key2"] = "value2"
```

**[Explore Multi-table & Transactions ↗](https://nanasqlite.disnana.com/en/guide#_4-transactions-multi-table)**

### ✨ v1.0.3+ Legacy Features

**Pydantic Support & Direct SQL:**

```python
# Pydantic support
db.set_model("user", User(name="Nana", age=20))

# Direct SQL execution
db.execute("SELECT * FROM data WHERE key LIKE ?", ("user%",))

# 22 new SQLite wrapper functions (sql_insert, sql_update, count, etc.)
db.sql_insert("users", {"name": "Alice", "age": 25})
```

---

---

---

## 日本語

### 🚀 特徴

- **dict風インターフェース**: おなじみの `db["key"] = value` 構文で操作
- **即時永続化**: 書き込みは即座にSQLiteに保存
- **スマートキャッシュ**: 遅延ロード（アクセス時）または一括ロード（起動時）
- **ネスト構造対応**: 30階層以上のネストしたdict/listをサポート
- **高性能**: WALモード、mmap、バッチ操作で最高速度を実現
- **セキュリティと安定性 (v1.2.0)**: SQL検証、ReDoS対策、厳格な接続管理を導入
- **設定不要**: 合理的なデフォルト設定でそのまま動作

### 📦 インストール

```bash
pip install nanasqlite
```

オプション機能付きのインストール:

```bash
# 高速化オプション（orjson + lru-dict）
pip install "nanasqlite[speed]"

# 暗号化機能（AES-GCM/ChaCha20/Fernet）
pip install "nanasqlite[encryption]"

# すべてのランタイム用オプションを一括インストール
pip install "nanasqlite[all]"

# 開発用ツール（pytest, ruff, mypy, tox等）
pip install -e ".[dev]"
```

### ⚡ クイックスタート

```python
from nanasqlite import NanaSQLite

# データベースを作成または開く
db = NanaSQLite("mydata.db")

# dictのように使う
db["user"] = {"name": "Nana", "age": 20, "tags": ["admin", "active"]}
print(db["user"])  # {'name': 'Nana', 'age': 20, 'tags': ['admin', 'active']}

# データは自動的に永続化
db.close()

# 後で再度開いても、データはそのまま！
db = NanaSQLite("mydata.db")
print(db["user"]["name"])  # 'Nana'
```

### 🔧 高度な使い方

```python
# 一括ロードで繰り返しアクセスを高速化
db = NanaSQLite("mydata.db", bulk_load=True)

# バッチ操作で高速な読み書き
db.batch_update({"k1": "v1", "k2": "v2"})
results = db.batch_get(["k1", "k2"])

# コンテキストマネージャ対応
with NanaSQLite("mydata.db") as db:
    db["temp"] = "value"
```

### 📚 ドキュメント

- **[公式サイト ↗](https://nanasqlite.disnana.com/)** (推奨)
- [スタートアップガイド](https://nanasqlite.disnana.com/guide)
- [APIリファレンス (同期)](https://nanasqlite.disnana.com/api_sync)
- [APIリファレンス (非同期)](https://nanasqlite.disnana.com/api_async)
- [ベンチマーク履歴 📊](https://nanasqlite.disnana.com/dev/bench/)
- [移行ガイド (v1.1.x から v1.2.0)](MIGRATION_GUIDE.md)

### ✨ v1.5.x 新機能 (Ultimate Hooks)

**Ultimate Hooks: 柔軟なバリデーションと外部制約アーキテクチャ**
データベースの読み書き・削除をカスタムフックや組み込み制約でインターセプトできます。データの検証、一意性の保証、外部キー制約のチェック、Pydantic とのシームレスな統合などが容易に行えます。

```python
from nanasqlite import NanaSQLite
from nanasqlite.hooks import UniqueHook, CheckHook, PydanticHook
from pydantic import BaseModel

class UserConfig(BaseModel):
    version: int
    theme: str

db = NanaSQLite("config.db")
db.add_hook(UniqueHook("email"))
db.add_hook(CheckHook(lambda k, v: v.get("age", 0) >= 18, "Age must be >= 18"))
db.add_hook(PydanticHook(UserConfig))

# dict を書き込むと、自動的に検証され Pydantic モデルとして読み出せます！
db["user_conf"] = {"version": 1, "theme": "dark", "email": "test@example.com", "age": 20}
config = db["user_conf"]
print(config.theme)  # 'dark'
```

### ✨ v1.4.x 新機能 (v2 アーキテクチャ)

**v2 アーキテクチャ: ノンブロッキング・バックグラウンド永続化**
KVSの書き込みをすべて「Write-Back Cache（メモリ優先更新）」として処理し、SQLiteへの保存をバックグラウンドスレッドで非同期に行うオプションアーキテクチャを導入しました。読み込みレイテンシも引き続きゼロコストです。

```python
# v2モードを有効化（1秒ごとのバックグラウンドフラッシュ）
db = NanaSQLite("high_load.db", v2_mode=True, flush_mode="time", flush_interval=1.0)

# オプション: V2Configを使用して設定をひとまとめに（v1.4.1+）
from nanasqlite import V2Config
cfg = V2Config(flush_mode="time", flush_interval=5.0, enable_metrics=True)
db = NanaSQLite("mydata.db", v2_mode=True, v2_config=cfg)

# どんなに重いI/Oが発生しても、メインスレッドは一切ブロックされません！
db["heavy_key"] = validate_and_compute_data()
```

> ⚠️ **警告**: v2モードは「単一プロセス」システム専用に設計されています。Gunicornの複数ワーカー構成などでv2モードを使用すると、複数のバックグラウンドスレッドが同時にSQLiteファイルを上書きし、致命的なデータ破損を引き起こします。

### ✨ v1.3.x 新機能

- **キャッシュ戦略**: LRU / TTL サポート ([ドキュメント](https://nanasqlite.disnana.com/guide#lesson-10-キャッシュ戦略))
- **データ暗号化**: AES-GCM / ChaCha20 / Fernet ([ドキュメント](https://nanasqlite.disnana.com/guide#lesson-11-暗号化))
- **永続化 TTL**: SQLite上のデータの自動消去。
- **ロックタイムアウト** (v1.3.4b1): ロックが一定時間内に取得できない場合に `NanaSQLiteLockError` を送出。マルチスレッドでのハング防止に最適。
- **バックアップ / リストア** (v1.3.4b1): APSW の SQLite バックアップ API によるオンラインバックアップと、一発でのリストアをサポート。
- **セキュリティ監査・強化** (v1.3.4): column_type ホワイトリスト検証、AEAD nonce 検証、全 dict メソッドのクローズ済みインスタンス安全性。詳細は [CHANGELOG](CHANGELOG.md) を参照。
- **セキュリティ監査・v2 バグ修正** (v1.4.0): `create_table()` カラム型SQLインジェクション修正、V2Engineコールバック順序修正、非同期子インスタンス属性継承修正、v2モード読み取りクエリバイパス修正。詳細は [CHANGELOG](CHANGELOG.md) を参照。
- **致命的バグ修正・性能向上** (v1.4.1): `upsert()` / `aupsert()` における `AttributeError` を修正。LRU/TTL キャッシュへのネガティブキャッシュ導入による性能向上。詳細は [CHANGELOG](CHANGELOG.md) を参照。
- **リファクタリング・品質向上** (v1.4.1): `V2Config` の導入による引数管理の整理と、Docker CI環境の安定化を実施。

```python
# ロックタイムアウト
db = NanaSQLite("app.db", lock_timeout=2.0)

# バックアップ
db.backup("snapshot.db")

# リストア
db.restore("snapshot.db")
```

### ✨ v1.2.0 新機能

**セキュリティ強化と厳格な接続管理:**

```python
# v1.2.0 セキュリティ機能
db = NanaSQLite("mydata.db", 
    strict_sql_validation=True,  # 未許可のSQL関数を禁止
    max_clause_length=500        # SQLの長さを制限してReDoSを防止
)

# v1.2.0 読み取り専用接続プール（非同期のみ）
async with AsyncNanaSQLite("mydata.db", read_pool_size=5) as db:
    # 重い読み取り操作（query, fetch_all）は自動的にプールを使用
    results = await asyncio.gather(
        db.query("logs", where="level=?", parameters=("ERROR",)),
        db.query("logs", where="level=?", parameters=("INFO",))
    )

# 厳格な接続管理
db.close()
# 無効化されたインスタンスへのアクセスは NanaSQLiteClosedError を送出します。
```

**[セキュリティ詳細を見る ↗](https://nanasqlite.disnana.com/guide#_2-強力なセキュリティ-v1-2-0-)**

### ✨ v1.1.0 新機能

**同一データベース内の複数テーブルを接続共有で安全に操作:**

```python
from nanasqlite import NanaSQLite

# メインテーブルインスタンスを作成
main_db = NanaSQLite("mydata.db", table="users")

# 同じ接続を共有する別のテーブルインスタンスを取得
products_db = main_db.table("products")
orders_db = main_db.table("orders")

# 各テーブルは独立したキャッシュと操作を持つ
main_db["user1"] = {"name": "Alice"}
products_db["prod1"] = {"name": "Laptop"}
```

**オプションのデータ暗号化 (v1.3.1a1+):**

```python
from nanasqlite import NanaSQLite

# 事前にインストール: pip install nanasqlite[encryption]
db = NanaSQLite("secure.db", encryption_key=b"your-32-byte-key") # デフォルトで AES-GCM

# モードを明示的に指定する場合
db_chacha = NanaSQLite("secure_cc.db", 
    encryption_key=b"your-32-byte-key", 
    encryption_mode="chacha20"
)

# SQLite内では暗号化されますが、メモリ上（キャッシュ）では平文で高速に扱えます
db["secret"] = {"password": "123"}
```

**トランザクションサポートとエラーハンドリング (v1.1.0+):**

```python
from nanasqlite import NanaSQLite, NanaSQLiteTransactionError

with db.transaction():
    db["key1"] = "value1"
    db["key2"] = "value2"
```

**[マルチテーブルと非同期を詳しく ↗](https://nanasqlite.disnana.com/guide#_4-トランザクションとマルチテーブル)**

### ✨ v1.0.3+ レガシー機能

**Pydantic互換性と直接SQL実行:**

```python
# Pydantic互換性
db.set_model("user", User(name="Nana", age=20))

# 直接SQL実行
db.execute("SELECT * FROM data WHERE key LIKE ?", ("user%",))

# 22種類のSQLiteラッパー関数 (sql_insert, sql_update, count等)
db.sql_insert("users", {"name": "Alice", "age": 25})
```

---

---

## License

MIT License - see [LICENSE](LICENSE) for details.
