Mirror SQL
SqlMirror mengirim koleksi storh ke tabel SQLite atau MySQL agar Anda dapat menjalankan join, pengurutan lintas tabel, pencarian substring, dan pelaporan di SQL sementara file tetap menjadi sumber kanonis. Anggap saja sebagai indeks sekunder yang kebetulan berupa basis data: mirror diturunkan dari data sumber, dapat dibuang, dan bisa dibangun ulang kapan saja. ext-pdo dengan driver sqlite atau mysql hanya diperlukan saat Anda menggunakannya; paket inti tetap bebas dependensi.
Penyiapan
use Storh\DocStore;
use Storh\Schema;
use Storh\SqlMirror;
$schema = Schema::collection('pages')
->string('slug')->unique()
->string('kind')->index()
->int('publishedAt')->range();
$pages = new DocStore($root, 'pages', schema: $schema);
$events = new Storh\SegmentedLog($root, 'events');
$mirror = new SqlMirror(new PDO('sqlite:' . $root . '/mirror.db'), 'app_');
$mirror->collection($pages, 'pages', $schema);
$mirror->collection($events, 'events');
$mirror->install();
$mirror->push();
Kedua mesin dapat di-mirror: koleksi DocStore dan aliran SegmentedLog (hanya rekaman aktif yang dikirim). Memberikan Schema bersifat opsional dan tidak bergantung pada penyimpanan: kolom string/int/float/bool yang dideklarasikan menjadi kolom bertipe dan terindeks; semua yang lain tetap dapat dikueri di kolom data JSON. Kolom mixed tidak dapat ditandai untuk pengindeksan.
Koneksi
Konstruktor menerima handle PDO (driver sqlite atau mysql, dideteksi secara otomatis), handle mysqli, atau implementasi SqlMirrorConnection apa pun:
$mirror = new SqlMirror(new PDO('sqlite:' . $root . '/mirror.db'));
$mirror = new SqlMirror(new PDO('mysql:host=127.0.0.1;dbname=app;charset=utf8mb4', $user, $password));
$mirror = new SqlMirror(new mysqli('127.0.0.1', $user, $password, 'app'), 'wp_storh_');
Jalur mysqli berfungsi dengan kedua mode pelaporan kesalahan mysqli, sehingga handle yang sudah ada dari aplikasi host (misalnya $wpdb->dbh milik WordPress) dapat digunakan kembali apa adanya. Perilakunya sama di semua driver; kegagalan pengiriman membatalkan transaksi koleksi pada semuanya.
Setiap tabel memiliki id (kunci utama), hash (menggerakkan pengiriman inkremental), kolom bertipe, dan data (JSON rekaman lengkap).
Sinkronisasi
$mirror->push(); // reconcile: insert, update, delete as needed
$mirror->push('pages'); // reconcile a single collection
$mirror->flush('pages', $ids); // push or remove just these ids
$mirror->pull(); // write mirror rows back into the store
$mirror->verify(); // report missing, stale, and orphaned rows
$mirror->rebuild(); // clear all rows and push from scratch
$mirror->uninstall(); // drop the mirror tables
push() membandingkan hash rekaman dengan mirror dan menulis setiap koleksi dalam satu transaksi, sehingga pengiriman yang crash atau gagal tidak pernah meninggalkan batch yang hanya diterapkan sebagian. Rekaman yang tidak berubah memerlukan satu SELECT ditambah pemindaian rekaman. push() berkala (misalnya dari tick cron) menjaga mirror tetap selaras; panggil flush() dengan id yang Anda tulis selama permintaan ketika pembaca perlu melihat hasil penulisan langsung dari mirror.
Membuat kueri
Kueri mirror menggunakan SQL biasa. table() mengembalikan nama tabel yang di-mirror:
$pdo->query(
'SELECT p.slug, COUNT(*) AS sales
FROM ' . $mirror->table('purchases') . ' o
INNER JOIN ' . $mirror->table('products') . ' p ON p.id = o.product_id
GROUP BY p.slug
ORDER BY sales DESC'
);
Mengembalikan data ke file
pull() membaca baris mirror dan menulisnya ke penyimpanan terdaftar, menurut urutan id. Fitur ini mendukung dua alur:
- Pemulihan: file hilang, tetapi mirror masih ada. Daftarkan penyimpanan baru dengan nama koleksi yang sama, lalu tarik data.
- Pengisian awal: baris yang dimuat ke tabel mirror oleh alat eksternal menjadi rekaman storh. Baris harus memuat id UUIDv7 yang valid dan objek di kolom
data; kolom bertipe diabaikan saat penarikan karenadataadalah rekaman lengkap.
$restored = new Storh\DocStore($root, 'pages');
$mirror = new SqlMirror($pdo);
$mirror->collection($restored, 'pages');
$mirror->pull(); // ['written' => n, 'unchanged' => m]
Penulisan hasil penarikan menggunakan mekanisme penyimpanan normal, sehingga validasi skema dan indeks unik tetap berlaku, dan setiap rekaman ditulis secara persisten. pull() tidak pernah menghapus rekaman lokal, dan rekaman dengan data yang sudah cocok dengan baris mirror akan dilewati. Setelah mengisi baris yang dimasukkan secara manual, jalankan push() sekali: tindakan ini menulis ulang baris tersebut dalam bentuk kanonis agar hash-nya selaras.
Ketentuan dan batasan
- File adalah sumber kanonis. Mirror merupakan keluaran hanya-baca; apa pun yang ditulis secara manual ke dalamnya akan ditimpa atau dihapus oleh
push()berikutnya. - Kolom unik dalam mirror berfungsi sebagai pengaman tambahan, bukan penegakan saat penulisan: duplikat yang diizinkan oleh file akan menyebabkan pengiriman gagal (dan dibatalkan sepenuhnya). Gunakan indeks unik storh untuk penegakan saat penulisan.
- Mirror tertinggal dari file hingga satu interval pengiriman, kecuali untuk id yang secara eksplisit Anda
flush(). - Mirroring tidak menambahkan transaksi ke storh itu sendiri. Penulisan atomik untuk beberapa rekaman tetap perlu ditangani oleh basis data sungguhan sebagai sistem pencatatan utama.
- Perubahan skema diterapkan dengan menghapus dan memasang ulang mirror:
uninstall(),install(),push().