mameyugo / jsonq
High-performance JSON file storage engine for PHP, powered by Rust
Fund package maintenance!
mameyugo
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 2
Watchers: 0
Forks: 0
Open Issues: 0
Language:Rust
Type:php-ext
Ext name:ext-jsonq
pkg:composer/mameyugo/jsonq
Requires
- php: >=8.1
README
π JsonQ
High-Performance JSON Storage Engine for PHP
MongoDB-style queries | JSON Schema validation | ACID transactions | 10x performance
Features β’ Quick Start β’ Documentation β’ Benchmarks β’ Contributing
π What is JsonQ?
JsonQ is a blazing-fast PHP extension written in Rust that provides a file-based JSON storage engine with MongoDB-style queries, schema validation, indexing, and transactions.
Why JsonQ?
- π 2-10x faster than pure PHP JSON handling
- πΎ File-based - No server required, zero configuration
- π MongoDB-style queries - Familiar syntax with 17+ operators
- β Schema validation - JSON Schema subset support
- π ACID transactions - Atomic operations with rollback
- π Indexing - Hash-based O(1) lookups
- π‘οΈ Thread-safe - OS-level file locking with fs2
- β‘ SIMD-accelerated - Fast parsing with simd-json
β¨ Features
Core Storage
- β CRUD Operations with dot-notation path access
- β Atomic Writes (tmp + fsync + rename) for crash safety
- β Memory-mapped I/O for zero-copy reads
- β Arc-based Caching with mtime invalidation
- β Compression Support (Gzip, Zstd) - v0.3.0
Query Engine
- β
MongoDB-style Matching:
$eq,$ne,$gt,$gte,$lt,$lte,$in,$nin - β
String Operators:
$contains,$startsWith,$endsWith,$regex - β
Logical Operators:
$and,$or,$not,$nor - β
Array Operators:
$size,$all,$elemMatch - β
Type Checking:
$exists,$type - β
Field Projection:
select()for whitelisting fields - v0.3.0 - β Query Optimizer: Intelligent index selection - v0.3.0
Fluent Query Builder
- β
Chainable Methods:
where(),orWhere(),orderBy(),limit(),skip() - β Filtering: Comparison, string, array operators
- β Sorting: Ascending/descending on any field
- β
Pagination:
skip+limitsupport
Aggregation & Analysis
- β
Functions:
sum(),avg(),min(),max(),count() - β
Grouping:
groupBy()with field-based grouping - β
Field Extraction:
pluck()for column extraction
Validation & Schema
- β JSON Schema Validation (subset)
- β Type Constraints: string, number, boolean, array, object
- β String Formats: email, URL, IPv4, date, UUID
- β Number Constraints: min, max, multipleOf
- β Array Constraints: minItems, maxItems, uniqueItems
- β Required Fields and enum validation
- β Conditional Logic: if/then/else, oneOf, anyOf
Indexing
- β Single Field Indexes: O(1) equality lookups
- β Compound Indexes: Multi-field indexing
- β Hash-based: MD5 hashing for fast lookups
- β
Auto-optimization: Automatic use in
find()queries
Transactions
- β ACID Guarantees: Atomic, Consistent, Isolated, Durable
- β Begin/Commit/Rollback: Full transaction support
- β Isolation: Transaction-local changes until commit
Advanced Features (v0.3.0)
- β Safe Regex - ReDoS protection with backtracking limits
- β Metrics API - Real-time observability (reads, writes, cache stats, latency)
- β Compression - Transparent Gzip/Zstd support
- β Query Optimizer - Intelligent index selection for complex queries
Collection Methods (NEW - v0.3.0)
- β
select(fields)- Project specific fields (whitelist) - β³
except(fields)- Exclude specific fields (blacklist) - β³
column(field)- Extract values from single column - β³
chunk(size)- Split results into groups - β³
implode(field, separator)- Join column values into string - β³
keys(path)- Get object keys - β³
values(path)- Get object values - β³
toJson(pretty)- Serialize results to JSON string
Legend: β Confirmed | β³ Pending verification
π Quick Start
Installation
Via APT (Debian/Ubuntu)
# Add repository curl -fsSL https://mameyugo.github.io/JsonQ/jsonq-archive-keyring.gpg | sudo gpg --dearmor -o /usr/share/keyrings/jsonq-archive-keyring.gpg echo "deb [signed-by=/usr/share/keyrings/jsonq-archive-keyring.gpg] https://mameyugo.github.io/JsonQ stable main" | sudo tee /etc/apt/sources.list.d/jsonq.list # Install sudo apt update sudo apt install php8.3-jsonq # Enable extension php -m | grep jsonq
Via Curl (Quick Install)
curl -fsSL https://raw.githubusercontent.com/mameyugo/JsonQ/main/scripts/install.sh | sudo bash
Build from Source
# Install Rust curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # Clone and build git clone https://github.com/mameyugo/JsonQ.git cd JsonQ cargo build --release # Install sudo cp target/release/libjsonq.so $(php-config --extension-dir)/jsonq.so echo "extension=jsonq.so" | sudo tee /etc/php/8.3/mods-available/jsonq.ini sudo phpenmod jsonq
Hello World
<?php use JsonQ\Store; // Create store $store = new Store('data.json'); // Write data $store->set('users', [ ['id' => 1, 'name' => 'Alice', 'role' => 'admin', 'age' => 30], ['id' => 2, 'name' => 'Bob', 'role' => 'user', 'age' => 25], ['id' => 3, 'name' => 'Charlie', 'role' => 'user', 'age' => 35], ]); // Query with MongoDB-style syntax $admins = $store->find('users', ['role' => 'admin']); // Returns: [['id' => 1, 'name' => 'Alice', ...]] // Fluent query builder $results = $store->executeQuery('users', [ 'where' => [ ['field' => 'age', 'op' => '>=', 'value' => 25] ], 'order_by' => ['field' => 'age', 'direction' => 'desc'], 'limit' => 10 ]); // Aggregation $avgAge = $store->aggregate('users', ['avg' => 'age']); // Returns: ['avg' => 30.0] echo "β JsonQ is working!\n";
π API Reference
Basic Operations
CRUD
// Create/Update $store->set('user.profile', ['name' => 'Alice', 'age' => 30]); // Read $profile = $store->get('user.profile'); // Returns: ['name' => 'Alice', 'age' => 30] // Check existence $exists = $store->has('user.profile'); // true // Delete $store->remove('user.profile'); // Count elements $count = $store->count('users'); // 3
Array Operations
// Push to array $store->push('users', ['id' => 4, 'name' => 'Dave']); // Merge objects $store->merge('config', ['debug' => true]); // Increment/Decrement $store->increment('stats.views'); // views++ $store->decrement('inventory.stock', 5); // stock -= 5
MongoDB-style Queries
Comparison Operators
// Greater than $adults = $store->find('users', ['age' => ['$gt' => 18]]); // Range queries $midAge = $store->find('users', [ 'age' => ['$gte' => 25, '$lte' => 35] ]); // Not equal $active = $store->find('users', ['status' => ['$ne' => 'deleted']]);
Array Operators
// In array $roles = $store->find('users', [ 'role' => ['$in' => ['admin', 'moderator']] ]); // Not in array $regular = $store->find('users', [ 'role' => ['$nin' => ['admin', 'guest']] ]); // Array size $teams = $store->find('projects', [ 'members' => ['$size' => 5] ]);
String Operators
// Contains substring $gmailUsers = $store->find('users', [ 'email' => ['$contains' => '@gmail.com'] ]); // Starts with $adminUsers = $store->find('users', [ 'username' => ['$startsWith' => 'admin_'] ]); // Ends with $txtFiles = $store->find('files', [ 'name' => ['$endsWith' => '.txt'] ]); // Regex matching (with ReDoS protection) $phoneNumbers = $store->find('contacts', [ 'phone' => ['$regex' => '^\+1-\d{3}-\d{3}-\d{4}$'] ]);
Logical Operators
// AND (implicit) $seniorAdmins = $store->find('users', [ 'role' => 'admin', 'age' => ['$gte' => 30] ]); // OR $privileged = $store->find('users', [ '$or' => [ ['role' => 'admin'], ['role' => 'moderator'] ] ]); // NOT $notGuests = $store->find('users', [ '$not' => ['role' => 'guest'] ]); // Complex nested logic $results = $store->find('users', [ '$and' => [ ['age' => ['$gte' => 18]], ['$or' => [ ['role' => 'admin'], ['verified' => true] ]] ] ]);
Fluent Query Builder
$results = $store->executeQuery('products', [ // Filtering 'where' => [ ['field' => 'price', 'op' => '>', 'value' => 100], ['field' => 'inStock', 'op' => '=', 'value' => true] ], // Sorting 'order_by' => [ 'field' => 'price', 'direction' => 'desc' // or 'asc' ], // Pagination 'skip' => 10, // Offset 'limit' => 20, // Max results // Projection (v0.3.0) 'select' => ['name', 'price', 'category'] ]);
Available Operators:
- Comparison:
=,!=,>,>=,<,<= - Array:
in,not in - String:
contains,startsWith,endsWith - Range:
between(expects array[min, max])
Aggregation
// Single aggregation $total = $store->aggregate('orders', ['sum' => 'amount']); // Returns: ['sum' => 15750.50] // Multiple aggregations $stats = $store->aggregate('products', [ 'sum' => 'price', 'avg' => 'price', 'min' => 'price', 'max' => 'price', 'count' => 'id' ]); // Returns: ['sum' => 5000, 'avg' => 250, 'min' => 50, 'max' => 1000, 'count' => 20] // Group by $byCategory = $store->groupBy('products', 'category'); // Returns: ['electronics' => [...], 'books' => [...]] // Extract column values $names = $store->pluck('users', 'name'); // Returns: ['Alice', 'Bob', 'Charlie']
Collection Methods (NEW)
// Select specific fields (projection) $results = $store->executeQuery('users', [ 'select' => ['name', 'email'] // Only return name and email ]); // Extract column values $emails = $store->column('users', 'email'); // Returns: ['alice@example.com', 'bob@example.com', ...] // Split into chunks $chunks = $store->chunk('users', 10); // Returns: [[user1..10], [user11..20], ...] // Join column values $nameList = $store->implode('users', 'name', ', '); // Returns: "Alice, Bob, Charlie, Dave" // Get object keys $keys = $store->keys('user.profile'); // Returns: ['name', 'email', 'age', 'verified'] // Get object values $values = $store->values('user.profile'); // Returns: ['Alice', 'alice@example.com', 30, true] // Serialize to JSON $json = $store->toJson('users'); $prettyJson = $store->toJson('users', true); // Pretty-print
Schema Validation
// Define schema $schema = [ 'type' => 'object', 'required' => ['name', 'email', 'age'], 'properties' => [ 'name' => [ 'type' => 'string', 'minLength' => 2, 'maxLength' => 50 ], 'email' => [ 'type' => 'string', 'format' => 'email' ], 'age' => [ 'type' => 'integer', 'minimum' => 18, 'maximum' => 120 ], 'role' => [ 'type' => 'string', 'enum' => ['admin', 'user', 'guest'] ] ] ]; // Validate single document $user = ['name' => 'Alice', 'email' => 'alice@example.com', 'age' => 30]; $isValid = $store->validate($user, $schema); // Returns: true // Validate collection $result = $store->validateCollection('users', $schema); // Returns: ['valid' => true, 'errors' => []]
Supported Constraints:
- Types: string, number, integer, boolean, array, object, null
- String: minLength, maxLength, pattern, format (email, url, ipv4, date, uuid)
- Number: minimum, maximum, multipleOf
- Array: minItems, maxItems, uniqueItems, items
- Object: required, properties, additionalProperties
- Enum: Fixed set of allowed values
- Conditional: if/then/else, oneOf, anyOf
Indexing
// Create single-field index $store->createIndex('users', 'email'); // Create compound index $store->createCompoundIndex('orders', ['customerId', 'status']); // Direct index lookup (O(1)) $user = $store->indexLookup('users', 'email', 'alice@example.com'); // List all indexes $indexes = $store->listIndexes(); // Returns: ['users.email' => 'single', 'orders.customerId+status' => 'compound'] // Drop index $store->dropIndex('users.email'); // Drop all indexes $store->dropAllIndexes();
Performance Impact:
- Indexed
find()queries: ~10x faster - Index memory overhead: ~2-5% of data size
- Write penalty: <5% slower (hash computation)
Transactions
try { // Begin transaction $store->begin(); // Perform multiple operations $store->set('accounts.A', ['balance' => 900]); $store->set('accounts.B', ['balance' => 1100]); $store->set('logs', ['transfer' => 100]); // Commit atomically $store->commit(); echo "β Transaction committed\n"; } catch (Exception $e) { // Rollback on error $store->rollback(); echo "β Transaction rolled back: " . $e->getMessage() . "\n"; }
Guarantees:
- Atomicity: All changes commit together or none
- Consistency: Schema validation enforced
- Isolation: Changes invisible until commit
- Durability: fsync ensures disk persistence
Advanced Features (v0.3.0)
Compression
// Enable Zstd compression (best compression ratio + speed) $store->setOption('compression', 'zstd'); // Or use Gzip $store->setOption('compression', 'gzip'); // Disable compression $store->setOption('compression', 'none'); // Transparent decompression - reads work automatically $data = $store->get('users');
Compression Comparison:
| Method | Ratio | Speed | Best For |
|---|---|---|---|
| none | 1.0x | Fastest | Small files, frequent writes |
| gzip | 2-3x | Fast | Good balance |
| zstd | 2.5-4x | Fastest | Large files, best compression |
Metrics & Observability
// Get real-time metrics $metrics = $store->getMetrics(); echo "Reads: " . $metrics['reads'] . "\n"; echo "Writes: " . $metrics['writes'] . "\n"; echo "Cache Hits: " . $metrics['cache_hits'] . "\n"; echo "Cache Misses: " . $metrics['cache_misses'] . "\n"; echo "Hit Rate: " . $metrics['cache_hit_rate'] . "%\n"; echo "Avg Latency: " . $metrics['avg_latency_ms'] . "ms\n";
Tracked Metrics:
- Read/write counters
- Cache hit/miss ratio
- Average read latency
- Last operation timestamp
Backup & Restore
// Create backup $store->backup('/backups/data-' . date('Y-m-d') . '.json'); // Restore from backup $store->restore('/backups/data-2024-01-15.json'); // Get file stats $stats = $store->stats(); echo "File size: " . $stats['file_size'] . " bytes\n"; echo "Last modified: " . $stats['modified_at'] . "\n";
Global Configuration
// Get current config $config = jsonq_get_config(); // Set max file size jsonq_set_max_file_size('100M'); // or '1G', '500K' // Set allowed extensions jsonq_set_allowed_extensions('json,db'); // Set base path (security restriction) jsonq_set_base_path('/var/www/data'); // Clear base path restriction jsonq_clear_base_path();
ποΈ Performance
Benchmarks (vs Pure PHP)
| Operation | JsonQ | Pure PHP | Speedup |
|---|---|---|---|
| Parse JSON | 0.8ms | 5.2ms | 6.5x faster |
| Find (no index) | 1.2ms | 12.5ms | 10.4x faster |
| Find (indexed) | 0.1ms | 12.5ms | 125x faster |
| Aggregate (sum) | 0.9ms | 8.7ms | 9.7x faster |
| Complex query | 2.1ms | 18.3ms | 8.7x faster |
| Write + fsync | 1.5ms | 3.2ms | 2.1x faster |
Benchmark environment: PHP 8.3, 10K documents, Intel i7-12700K
Optimization Tips
- Use Indexes: 10-100x speedup for equality lookups
- Enable Compression: 50-75% disk space reduction
- Batch Writes: Use transactions for multiple operations
- Monitor Metrics: Track cache hit rate and optimize queries
- Project Fields: Use
selectto return only needed data
ποΈ Architecture
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β PHP Application Layer β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β FFI (ext-php-rs)
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β Rust Core (JsonQ) β
βββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 1. Parser - simd-json (SIMD parsing) β
β 2. Cache - Arc + mtime invalidation β
β 3. Query Engine - MongoDB + JSONPath β
β 4. Index Manager - Hash-based O(1) lookups β
β 5. Transactions - ACID with rollback β
β 6. Storage - memmap2 + file locking β
β 7. Security - Path validation, ReDoS β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β File System
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β data.json + indexes/ β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
π€ Contributing
Contributions are welcome! Please read CONTRIBUTING.md for guidelines.
Development Setup
# Clone repository git clone https://github.com/mameyugo/JsonQ.git cd JsonQ # Install Rust curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # Build debug cargo build # Build release cargo build --release # Run Rust tests cargo test # Run PHP integration tests php tests/run_tests.php # Run benchmarks php examples/benchmark_v2.php
Project Structure
JsonQ/
βββ src/ # Rust implementation
β βββ conversion/ # PHP β Rust FFI
β βββ store/ # Storage engine
β βββ query/ # Query execution
β βββ index/ # Indexing system
β βββ validation/ # Schema validation
βββ tests/
β βββ integration/ # PHP integration tests
β βββ unit/ # Rust unit tests
βββ stubs/ # PHP IDE stubs
βββ docs/ # Documentation
π License
JsonQ is licensed under The PHP License, version 3.01.
π Acknowledgments
- simd-json - SIMD-accelerated JSON parsing
- ext-php-rs - Safe Rust-PHP FFI bindings
- serde - Serialization framework
- MongoDB - Query syntax inspiration
- The Rust and PHP communities
π Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Email: info@mameyugo.com
πΊοΈ Roadmap
v0.4.0 (Q2 2026)
- Complete collection methods (
except,column,chunk,implode,keys,values,toJson) - JSONPath full support
- Query optimizer improvements
- Multi-stage aggregation pipelines
v0.5.0 (Q3 2026)
- JOIN operations across collections
- Full-text search with stemming
- Watch API for change streams
- GraphQL-like query DSL
v1.0.0 (Q4 2026)
- Production-ready stable API
- >95% test coverage
- Comprehensive documentation
- Performance parity with MongoDB for common queries
Made with β€οΈ in Rust | Powered by π SIMD
β Star on GitHub β’ π Documentation β’ π Report Bug