Skip to content
Projects

orhanayd/noneDB has 35 GitHub stars and 1 forks. GitHot measured 0 stars in 24 hours. Last checked 2026-11-09 10:19 UTC. Last push 2026-11-01.

How these counts are measured

noneDB is a lightweight, zero-dependency, file-based NoSQL database for PHP. It offers O(1) key lookups, MongoDB-style queries, R-tree spatial indexing, auto-sharding, atomic locking, and a fluent API—no server or setup required.

What you can do

  • One file, no dependencies: noneDB.php, PHP 8.1 or later
  • Records and queries: insert, find, update, delete; a fluent query builder with MongoDB-style operators, sorting, joins, grouping, aggregates and explain()
  • Indexes on disk: key lookups through key-index checkpoints, paged field indexes, a global field index per sharded database, R-tree spatial indexes for GeoJSON; a lookup reads only the pages it needs
  • Large databases: automatic sharding; queries on 10 million records run in PHP's default memory_limit (128 MB)

Measured star growth

in 24h
0
in 7 days
Collecting history
in 15 days
Collecting history
in 1 month
Collecting history

Net star changes over overlapping periods, not daily totals. Missing measurements are left blank. Stars measure attention, not software quality.

README.md

Original

noneDB

Version PHP Version License Concurrency

noneDB is an embedded document database for PHP: one file, no server, no extension. Include it and store records in a directory.

Features

  • One file, no dependencies: noneDB.php, PHP 8.1 or later
  • Records and queries: insert, find, update, delete; a fluent query builder with MongoDB-style operators, sorting, joins, grouping, aggregates and explain()
  • Indexes on disk: key lookups through key-index checkpoints, paged field indexes, a global field index per sharded database, R-tree spatial indexes for GeoJSON; a lookup reads only the pages it needs
  • Large databases: automatic sharding; queries on 10 million records run in PHP's default memory_limit (128 MB)
  • Safe under processes and crashes: database locks across processes, writes that throw are undone, a killed process loses no acknowledged write, recovery is deterministic, damaged files are quarantined and never silently read
  • Durable mode: every write synced to stable storage before it returns (Linux)
  • Operations: online backup and one-commit restore, metrics, integrity checks, key rotation
  • Migration of databases written by noneDB 1.x to 3.x: a separate tool, noneDB-migrate.php

Requirements

  • PHP 8.1 to 8.5 (json and ctype, on by default; mbstring recommended)
  • A local file system (ext4, xfs, btrfs, APFS, NTFS). Network file systems and volumes shared by several machines are not supported: locks use flock().
  • Durable mode: Linux. Performance mode (the default): Linux, macOS, Windows.

Installation

composer require orhanayd/nonedb

or copy noneDB.php into your project:

require 'noneDB.php';

Quick start

$db = new noneDB(); // reads .nonedb (below)

$db->insert("users", ["name" => "John", "age" => 25, "email" => "[email protected]"]);
$db->insert("users", [["name" => "Jane", "age" => 31], ["name" => "Ali", "age" => 17]]);

$adults = $db->query("users")
    ->where(['age' => ['$gte' => 18]])
    ->sort('age', 'desc')
    ->limit(10)
    ->get();

$db->update("users", [["name" => "John"], ["set" => ["email" => "[email protected]"]]]);
$db->delete("users", ["name" => "Ali"]);

Configuration

Put a .nonedb file outside the web root, next to your entry script or in the project root:

{
    "secretKey": "YOUR_SECURE_RANDOM_STRING",
    "dbDir": "./db/"
}
  • secretKey: a random secret of your own: php -r "echo bin2hex(random_bytes(32));" prints one. Published placeholders, the one above too, are refused. It names the files of your databases, so they can't be guessed; it does not encrypt them. Keep dbDir out of the web root.
  • dbDir: where the databases live. A relative path is relative to the .nonedb file.
  • Or pass the settings as an array: new noneDB(['secretKey' => getenv('NONEDB_SECRET'), 'dbDir' => '/var/lib/app/db']).

Every setting (sharding, buffer, locks, durability, cache, permissions): docs/CONFIGURATION.md.

Queries

$db->insert("products", [
    ["name" => "Lamp", "price" => 120, "category" => "home", "stock" => 4, "tags" => ["featured"]],
    ["name" => "Phone", "price" => 480, "category" => "electronics", "stock" => 0, "tags" => []],
]);

$db->query("products")
    ->where([
        'price' => ['$gte' => 100, '$lte' => 500],
        'category' => ['$in' => ['electronics', 'home']],
        'stock' => ['$gt' => 0],
    ])
    ->sort('price', 'desc')
    ->get();

$db->query("products")->where(['tags' => ['$contains' => 'featured']])->count();
$db->query("products")->where(['name' => ['$like' => '^La']])->first();
Operator Matches Example
$eq, $ne equal / not equal (===) ['status' => ['$ne' => 'guest']]
$gt, $gte, $lt, $lte numbers and numeric strings by value, strings lexically ['age' => ['$gte' => 18]]
$in, $nin in / not in a list (===) ['role' => ['$in' => ['a', 'b']]]
$exists the field is there (or not) ['email' => ['$exists' => true]]
$like a pattern: ^ starts with, $ ends with ['name' => ['$like' => '^Jo']]
$regex a regular expression, case-insensitive ['email' => ['$regex' => '@gmail\\.com$']]
$contains an array element or a substring ['tags' => ['$contains' => 'featured']]

A field index makes equality, $in and range filters read only the matching records:

$db->createFieldIndex("products", "category");
$db->query("products")->where(['category' => 'home'])->explain(); // the plan: which index, which shards

The query reference: docs/QUERY.md.

Spatial queries

$db->createSpatialIndex("restaurants", "location");
$db->insert("restaurants", [
    'name' => 'Ottoman Kitchen',
    'open' => true,
    'location' => ['type' => 'Point', 'coordinates' => [28.9784, 41.0082]],
]);

$nearby = $db->query("restaurants")
    ->withinDistance('location', 28.9784, 41.0082, 5000)   // meters
    ->where(['open' => true])
    ->get();
$closest = $db->nearest("restaurants", "location", 28.9784, 41.0082, 10);

Geometries, rules and the R-tree: docs/SPATIAL.md.

Concurrency and crashes

  • Every operation locks its database: shared for reads, exclusive for writes, across processes. Concurrent writes are serialized; keys are never reused.
  • A write that throws leaves no record behind. A process that is killed loses no write that returned: complete lines are kept, an unterminated line is never a record and is moved to a verified quarantine file before the next writer cuts it, indexes replay the data after their checkpoints.
  • Files that don't fit their checksums or their records are never silently used: a database that can't be proven consistent fails with FORMAT_DAMAGED, and restore() from a backup is the way out.
  • A multi-record call (insert([...1000 records])) is not a transaction under a kill: a prefix of it may stay.
Mode A write that returned survives
'durability' => 'performance' (default) the process dying (SIGKILL, fatal error), as long as the operating system runs on
'durability' => 'durable' (Linux) a power loss as well: every sync is done before the write returns, or it fails

The contract: docs/DURABILITY.md; the file format: docs/STORAGE_FORMAT.md.

Operations

$backup = __DIR__ . '/backups/users-' . date('Ymd-His'); // outside dbDir
$db->backup("users", $backup);           // online: one consistent moment, verified, manifest last
$db->restore($backup, "users");          // one commit: the database is as it was, or restored

$db->checkIntegrity("users");            // a full read-only check
$db->getMetrics("users");                // cache, locks, checkpoints, sizes, state: cheap counters

Backups, restores, monitoring, key rotation and what to do when the engine refuses to work: docs/BACKUP_RESTORE.md, docs/OPERATIONS.md.

Upgrading from noneDB 1.x to 3.x

noneDB 4 reads only its own format and refuses a directory that holds older files (UNSUPPORTED_FORMAT): it never mistakes them for an empty database. Convert them once with the migration tool, which verifies every database and keeps a backup:

NONEDB_SECRET=... php noneDB-migrate.php --dir=./db --backup-dir=/backups --confirm-stopped

The procedure and every source format: docs/MIGRATION.md. What changed: CHANGES.md.

Performance

Measured against a fixed baseline with a statistical gate (two independent runs), on a database of 10 million records, and against SQLite: docs/PERFORMANCE.md.

Documentation

Document Contents
docs/API.md Every method, its results and errors
docs/QUERY.md The query builder
docs/SPATIAL.md Geospatial indexes and queries
docs/CONFIGURATION.md Settings
docs/DURABILITY.md What a successful, failed or uncertain write means
docs/STORAGE_FORMAT.md The files, normative
docs/BACKUP_RESTORE.md Backups and restores
docs/OPERATIONS.md Metrics, maintenance, failure situations
docs/MIGRATION.md Converting databases of earlier versions
docs/PERFORMANCE.md Measurements and how they are made
docs/ARCHITECTURE.md The source tree and the build
CHANGES.md Release notes

Runnable examples: examples/ (php examples/find.php).

Development

noneDB.php and noneDB-migrate.php are built from src/ and migrate/: edit those, never the artifacts.

composer build                         # php tools/build.php
php tools/make-migration-fixtures.php  # first: the databases the Migration suite reads (git history, PHP 7.4)
composer test                          # build check + every PHPUnit suite
vendor/bin/phpunit --testsuite Crash   # Unit, Integration, Crash, Concurrency, Differential, Migration
php tests/Docs/doctest.php             # the examples of the documentation, against noneDB.php

Disclaimer

noneDB is provided "as is", without warranty of any kind, as the MIT License states. To the fullest extent the law allows, its authors and contributors accept no liability for any loss or damage arising from its use, including lost or corrupted data, downtime, security incidents and lost profits.

You are responsible for deciding whether noneDB suits your use, testing it with your own data and workload, keeping backups you have verified, and protecting your keys and files. noneDB does not encrypt data. The test results and performance figures of this project describe the runs that produced them; they are no promise of how it will behave for you.

License

MIT License: see LICENSE.

Author

Orhan Aydogdu


"Hayatta en hakiki mürşit ilimdir."

"The truest guide in life is science."

— Mustafa Kemal Atatürk

Explore related projects