minidb-db 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- minidb_db-0.1.0/LICENSE +21 -0
- minidb_db-0.1.0/MANIFEST.in +11 -0
- minidb_db-0.1.0/PKG-INFO +407 -0
- minidb_db-0.1.0/README.md +374 -0
- minidb_db-0.1.0/app/__init__.py +1 -0
- minidb_db-0.1.0/app/database.py +79 -0
- minidb_db-0.1.0/app/demo.py +93 -0
- minidb_db-0.1.0/app/repositories.py +249 -0
- minidb_db-0.1.0/bench/__init__.py +1 -0
- minidb_db-0.1.0/bench/benchmark.py +127 -0
- minidb_db-0.1.0/minidb/__init__.py +79 -0
- minidb_db-0.1.0/minidb/aggregation.py +738 -0
- minidb_db-0.1.0/minidb/bson.py +151 -0
- minidb_db-0.1.0/minidb/collection.py +696 -0
- minidb_db-0.1.0/minidb/comparison.py +322 -0
- minidb_db-0.1.0/minidb/database.py +136 -0
- minidb_db-0.1.0/minidb/demo.py +212 -0
- minidb_db-0.1.0/minidb/engine.py +644 -0
- minidb_db-0.1.0/minidb/errors.py +68 -0
- minidb_db-0.1.0/minidb/index.py +389 -0
- minidb_db-0.1.0/minidb/objectid.py +105 -0
- minidb_db-0.1.0/minidb/paths.py +92 -0
- minidb_db-0.1.0/minidb/query.py +442 -0
- minidb_db-0.1.0/minidb/update.py +509 -0
- minidb_db-0.1.0/minidb/validation.py +246 -0
- minidb_db-0.1.0/minidb/webui/__init__.py +7 -0
- minidb_db-0.1.0/minidb/webui/__main__.py +46 -0
- minidb_db-0.1.0/minidb/webui/server.py +403 -0
- minidb_db-0.1.0/minidb/webui/static/app.css +255 -0
- minidb_db-0.1.0/minidb/webui/static/app.js +449 -0
- minidb_db-0.1.0/minidb/webui/static/index.html +145 -0
- minidb_db-0.1.0/minidb_db.egg-info/PKG-INFO +407 -0
- minidb_db-0.1.0/minidb_db.egg-info/SOURCES.txt +42 -0
- minidb_db-0.1.0/minidb_db.egg-info/dependency_links.txt +1 -0
- minidb_db-0.1.0/minidb_db.egg-info/entry_points.txt +2 -0
- minidb_db-0.1.0/minidb_db.egg-info/requires.txt +7 -0
- minidb_db-0.1.0/minidb_db.egg-info/top_level.txt +1 -0
- minidb_db-0.1.0/pyproject.toml +70 -0
- minidb_db-0.1.0/schemas/__init__.py +1 -0
- minidb_db-0.1.0/schemas/collections.py +114 -0
- minidb_db-0.1.0/setup.cfg +4 -0
- minidb_db-0.1.0/tests/test_minidb.py +700 -0
- minidb_db-0.1.0/tests/test_parity.py +427 -0
- minidb_db-0.1.0/tests/test_webui.py +239 -0
minidb_db-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 V1PASH
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
minidb_db-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: minidb-db
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A MongoDB-style document database written from scratch, with schema validation and a built-in web UI.
|
|
5
|
+
Author: V1PASH
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/V1PASH/Database
|
|
8
|
+
Project-URL: Repository, https://github.com/V1PASH/Database
|
|
9
|
+
Project-URL: Issues, https://github.com/V1PASH/Database/issues
|
|
10
|
+
Project-URL: Documentation, https://github.com/V1PASH/Database#readme
|
|
11
|
+
Keywords: database,document-database,nosql,mongodb,embedded-database,sqlite,query-engine
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Database
|
|
20
|
+
Classifier: Topic :: Database :: Database Engines/Servers
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
28
|
+
Requires-Dist: pyflakes>=3.0; extra == "dev"
|
|
29
|
+
Requires-Dist: build>=1.0; extra == "dev"
|
|
30
|
+
Requires-Dist: twine>=5.0; extra == "dev"
|
|
31
|
+
Requires-Dist: pymongo>=4.0; extra == "dev"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# minidb
|
|
35
|
+
|
|
36
|
+
[](https://github.com/V1PASH/Database/actions/workflows/ci.yml)
|
|
37
|
+
[](https://www.python.org/)
|
|
38
|
+
[](LICENSE)
|
|
39
|
+
|
|
40
|
+
**A MongoDB-style document database, written from scratch.**
|
|
41
|
+
|
|
42
|
+
You asked for a database of your own that behaves like MongoDB. This is one:
|
|
43
|
+
a document store with a real query engine, its own index structures, its own
|
|
44
|
+
aggregation pipeline and schema validation -- built on top of SQLite for
|
|
45
|
+
durability, but with none of MongoDB's behaviour handed to you for free.
|
|
46
|
+
|
|
47
|
+
The API is deliberately shaped like PyMongo's, so application code written
|
|
48
|
+
against minidb keeps working if you ever point it at a real MongoDB server.
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from minidb import Database
|
|
52
|
+
|
|
53
|
+
db = Database("app.db")
|
|
54
|
+
db.create_collection("users", validator={"required": ["name", "email"]})
|
|
55
|
+
|
|
56
|
+
db.users.insert_one({"name": "Ada", "email": "ada@example.com", "role": "admin"})
|
|
57
|
+
db.users.find_one({"email": "ada@example.com"})
|
|
58
|
+
db.users.update_one({"name": "Ada"}, {"$inc": {"logins": 1}})
|
|
59
|
+
db.users.create_index("email", unique=True)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Is it really MongoDB-compatible?
|
|
65
|
+
|
|
66
|
+
Rather than guess, minidb is tested **against a real MongoDB server**. Every
|
|
67
|
+
filter, update and pipeline in `tests/test_parity.py` is executed against
|
|
68
|
+
both databases and the results compared:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
$ pytest tests/ -q
|
|
72
|
+
163 passed
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
That suite is what pinned down the subtle parts of MongoDB's behaviour --
|
|
76
|
+
for example, that range operators *type bracket* (a `null` field never
|
|
77
|
+
matches `{"age": {"$lt": 36}}`), that `$min`/`$max` ignore nulls, and that
|
|
78
|
+
`$addToSet` ordering is deliberately unspecified.
|
|
79
|
+
|
|
80
|
+
If you have MongoDB running locally, the parity tests use it. Without it,
|
|
81
|
+
they skip and the rest of the suite still runs.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Getting started
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
pip install minidb-db # from PyPI
|
|
89
|
+
pip install -e ".[dev]" # from a clone, with the test tools
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Then:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
python -m app.demo # a guided tour of the example app
|
|
96
|
+
python -m bench.benchmark # what indexes buy you
|
|
97
|
+
pytest tests/ -q # the test suite
|
|
98
|
+
minidb-ui app.db # browse the database in your browser
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The import name is `minidb`, whatever the package is called:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from minidb import Database
|
|
105
|
+
|
|
106
|
+
db = Database("app.db")
|
|
107
|
+
db.users.insert_one({"name": "Ada", "email": "ada@example.com"})
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The package has **no runtime dependencies** -- it is pure Python on top of
|
|
111
|
+
the standard library's `sqlite3`.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## What is in the box
|
|
116
|
+
|
|
117
|
+
| Area | What works |
|
|
118
|
+
|---|---|
|
|
119
|
+
| **Documents** | JSON documents with `ObjectId`, `datetime`, `Decimal`, `UUID` and `bytes` support |
|
|
120
|
+
| **Queries** | `$eq` `$ne` `$gt` `$gte` `$lt` `$lte` `$in` `$nin` `$exists` `$regex` `$options` `$all` `$size` `$elemMatch` `$not` `$type` `$mod`, plus `$and` `$or` `$nor` |
|
|
121
|
+
| **Paths** | Dotted paths (`"address.city"`), implicit traversal into arrays |
|
|
122
|
+
| **Updates** | `$set` `$unset` `$inc` `$mul` `$min` `$max` `$push` `$addToSet` `$pop` `$pull` `$pullAll` `$rename` `$setOnInsert` `$currentDate`, `arrayFilters`, upserts, replacements |
|
|
123
|
+
| **Aggregation** | `$match` `$project` `$addFields`/`$set` `$unset` `$group` `$sort` `$limit` `$skip` `$count` `$unwind` `$replaceRoot` `$sortByCount` and ~30 expressions |
|
|
124
|
+
| **Schemas** | Per-collection validators: types, `required`, `enum`, `pattern`, numeric bounds, array rules, `additionalProperties`, and **`default` values** |
|
|
125
|
+
| **Indexes** | Single-field and **compound** indexes (unique or not), multikey (array) indexes, order-preserving keys that serve equality *and* range lookups |
|
|
126
|
+
| **Writes** | Transactions with rollback, unique-constraint enforcement, `ObjectId` generation |
|
|
127
|
+
| **Extras** | `explain()` to see the query plan, `distinct()`, `count_documents()`, cursors with `sort`/`skip`/`limit` |
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## How it works
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
your app
|
|
135
|
+
|
|
|
136
|
+
minidb/collection.py the API (PyMongo shaped)
|
|
137
|
+
|
|
|
138
|
+
+----------+-----------+-------------------+
|
|
139
|
+
| | | |
|
|
140
|
+
query.py update.py aggregation.py validation.py
|
|
141
|
+
| | | |
|
|
142
|
+
+----------+-----------+-------------------+
|
|
143
|
+
|
|
|
144
|
+
index.py the planner + the key encoding
|
|
145
|
+
|
|
|
146
|
+
engine.py SQLite: documents, index tables, transactions
|
|
147
|
+
|
|
|
148
|
+
SQLite file
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Storage.** Documents are stored as JSON text, one row per document, keyed
|
|
152
|
+
by `(collection, id)`. SQLite gives durability, atomicity and crash safety
|
|
153
|
+
for free -- but every decision that defines *how the database behaves*
|
|
154
|
+
lives in this project, not in SQLite.
|
|
155
|
+
|
|
156
|
+
**Indexes.** minidb keeps its own index tables (`_minidb_index_entries`),
|
|
157
|
+
keyed by `(collection, index, encoded_key, document_id)` and stored
|
|
158
|
+
`WITHOUT ROWID`, so SQLite's B-tree does the work. The encoding is
|
|
159
|
+
**order preserving**: a value is mapped onto bytes whose byte order matches
|
|
160
|
+
MongoDB's BSON comparison order, which is what lets one index serve both
|
|
161
|
+
equality lookups and range scans. Each component of a key is
|
|
162
|
+
self-delimiting, so a compound key's components are compared one after the
|
|
163
|
+
other.
|
|
164
|
+
|
|
165
|
+
**The planner.** `index.py` looks at the filter, finds an indexed field it
|
|
166
|
+
can use, and hands the engine a plan (`eq`, `prefix` or `range`). The engine
|
|
167
|
+
then returns a *candidate set* -- always a **superset** of the matching
|
|
168
|
+
documents -- and the full filter is applied in Python afterwards. That
|
|
169
|
+
last step is what keeps the answers correct: an index may be an
|
|
170
|
+
approximation, but it can never change the result.
|
|
171
|
+
|
|
172
|
+
**Type ordering.** MongoDB compares values of different types using a fixed
|
|
173
|
+
order (null < numbers < strings < objects < arrays < ... < dates). minidb
|
|
174
|
+
reproduces that in `comparison.py`, which is why `{"a": {"$gt": 5}}` behaves
|
|
175
|
+
the same in both.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Using it
|
|
180
|
+
|
|
181
|
+
### Reading
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
db.users.find_one({"email": "ada@example.com"})
|
|
185
|
+
db.users.find({"age": {"$gte": 30}}, sort=[("age", -1)], limit=10)
|
|
186
|
+
db.users.find({"address.city": "London"}).sort("name").skip(20)
|
|
187
|
+
|
|
188
|
+
db.users.find_one({"name": "Ada"}, {"name": 1, "email": 1, "_id": 0})
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Writing
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
db.users.insert_one({"name": "Ada", "email": "ada@example.com"})
|
|
195
|
+
db.users.insert_many([...])
|
|
196
|
+
|
|
197
|
+
db.users.update_one({"_id": oid}, {"$set": {"role": "admin"}, "$inc": {"logins": 1}})
|
|
198
|
+
db.users.update_many({"role": "viewer"}, {"$set": {"active": False}})
|
|
199
|
+
db.users.replace_one({"_id": oid}, {"name": "Ada", "email": "a@b.com"})
|
|
200
|
+
|
|
201
|
+
db.users.update_one(
|
|
202
|
+
{"_id": oid},
|
|
203
|
+
{"$set": {"items.$[i].done": True}},
|
|
204
|
+
array_filters=[{"i.qty": {"$gte": 2}}],
|
|
205
|
+
)
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Aggregating
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
list(db.tasks.aggregate([
|
|
212
|
+
{"$match": {"project_id": project_id}},
|
|
213
|
+
{"$group": {"_id": "$assignee_id", "hours": {"$sum": "$estimate_hours"}}},
|
|
214
|
+
{"$sort": {"hours": -1}},
|
|
215
|
+
]))
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Schemas
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
db.create_collection("users", validator={
|
|
222
|
+
"required": ["name", "email"],
|
|
223
|
+
"additionalProperties": False,
|
|
224
|
+
"properties": {
|
|
225
|
+
"name": {"type": "string", "minLength": 1},
|
|
226
|
+
"email": {"type": "string", "format": "email"},
|
|
227
|
+
"role": {"type": "string", "enum": ["admin", "member"], "default": "member"},
|
|
228
|
+
},
|
|
229
|
+
})
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
A document that breaks the schema is rejected on write, with a message
|
|
233
|
+
pointing at the offending field. `default` values are filled in for you.
|
|
234
|
+
|
|
235
|
+
### Indexes
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
db.users.create_index("email", unique=True)
|
|
239
|
+
db.tasks.create_index([("project_id", 1), ("status", 1)]) # compound
|
|
240
|
+
db.users.list_indexes()
|
|
241
|
+
db.users.explain({"email": "ada@example.com"})
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
```
|
|
245
|
+
{'strategy': 'IXSCAN', 'index': 'email_1', 'documentsExamined': 1, 'totalDocuments': 20000}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
A **compound index** covers several fields. Its key is the concatenation of
|
|
249
|
+
self-delimiting component keys, so the components are compared one after
|
|
250
|
+
the other -- exactly like MongoDB, whose compound keys are compared
|
|
251
|
+
component by component. That is what lets one index answer:
|
|
252
|
+
|
|
253
|
+
| Query | What the index does |
|
|
254
|
+
|---|---|
|
|
255
|
+
| `{"a": 1, "b": 2}` | exact key lookup |
|
|
256
|
+
| `{"a": 1}` | prefix scan |
|
|
257
|
+
| `{"a": 1, "b": {"$gt": 5}}` | one contiguous range of keys |
|
|
258
|
+
| `{"b": 2}` | not usable -- `a` comes first, so the index cannot help |
|
|
259
|
+
|
|
260
|
+
On 20,000 documents, a compound index turned a 55.9 ms scan into a
|
|
261
|
+
0.01 ms lookup.
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## The web UI
|
|
266
|
+
|
|
267
|
+
Every database deserves a way to look at it. minidb ships with one:
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
minidb-ui app.db # or: python -m minidb.webui app.db
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
No data yet? Seed a sample database first:
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
python -c "from minidb import Database; from minidb.demo import seed; seed(Database('demo.db'))"
|
|
277
|
+
minidb-ui demo.db
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
<img src="docs/ui.png" alt="The minidb web UI" width="100%">
|
|
281
|
+
|
|
282
|
+
It opens on `http://127.0.0.1:8080` and gives you:
|
|
283
|
+
|
|
284
|
+
- **Browse** -- every collection with its document count, its indexes, and a
|
|
285
|
+
filter/sort/limit toolbar
|
|
286
|
+
- **Query** -- a filter, a projection, a sort, a limit and a skip
|
|
287
|
+
- **Aggregate** -- a whole aggregation pipeline
|
|
288
|
+
- **Insert** -- a document, validated by the collection's schema
|
|
289
|
+
- **Document detail** -- click any document for highlighted JSON, and delete
|
|
290
|
+
it from there
|
|
291
|
+
|
|
292
|
+
The server is built on `http.server` from the standard library, so it has no
|
|
293
|
+
dependencies of its own. It binds to localhost and has **no authentication**:
|
|
294
|
+
it is a local development tool, not something to expose to a network.
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## The example application
|
|
299
|
+
|
|
300
|
+
`app/` is a small but complete backend built on the database:
|
|
301
|
+
|
|
302
|
+
| File | What it holds |
|
|
303
|
+
|---|---|
|
|
304
|
+
| `schemas/collections.py` | The schemas for `users`, `projects`, `tasks`, `notes` |
|
|
305
|
+
| `app/database.py` | Opening the database, creating collections and indexes |
|
|
306
|
+
| `app/repositories.py` | The access layer the application actually uses |
|
|
307
|
+
| `app/demo.py` | A runnable tour (`python -m app.demo`) |
|
|
308
|
+
|
|
309
|
+
Keeping every database call inside the repositories means the storage
|
|
310
|
+
engine can be swapped later without touching the rest of the app -- swap
|
|
311
|
+
`minidb.Database` for `pymongo.MongoClient` and the repositories keep
|
|
312
|
+
working.
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Differences from MongoDB (the honest list)
|
|
317
|
+
|
|
318
|
+
minidb is a from-scratch implementation, so some things are missing or
|
|
319
|
+
behave differently. Everything here is a deliberate limitation, not a bug:
|
|
320
|
+
|
|
321
|
+
**Not implemented**
|
|
322
|
+
|
|
323
|
+
- Descending index directions -- compound indexes are ascending only, and
|
|
324
|
+
`create_index` says so rather than quietly ignoring the direction.
|
|
325
|
+
- `$lookup`, `$facet`, `$out`, `$merge`, `$graphLookup`, `$unionWith`.
|
|
326
|
+
- The positional `$` operator in updates (`"items.$.qty"`). `arrayFilters`
|
|
327
|
+
(`"items.$[i].qty"`) do work.
|
|
328
|
+
- Change streams, geospatial queries, full-text search, map/reduce.
|
|
329
|
+
- A network protocol: minidb is an embedded library, not a server.
|
|
330
|
+
- Replication, sharding, and transactions spanning collections.
|
|
331
|
+
|
|
332
|
+
**Different on purpose**
|
|
333
|
+
|
|
334
|
+
- `find()` returns documents in insertion order, and sorting ties keep
|
|
335
|
+
insertion order. MongoDB does not promise an order for ties.
|
|
336
|
+
- `$addToSet` keeps insertion order; MongoDB's order is unspecified.
|
|
337
|
+
- `additionalProperties: False` exempts `_id`, which minidb adds itself.
|
|
338
|
+
- The `_id` index is always present; there is no way to drop it.
|
|
339
|
+
- A document with several very large array fields in one compound index
|
|
340
|
+
needs too many key combinations to index completely. minidb keeps the
|
|
341
|
+
document findable through an overflow table rather than hiding it.
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## Continuous integration
|
|
346
|
+
|
|
347
|
+
Every push runs the suite on Python 3.11, 3.12 and 3.13 -- against a real
|
|
348
|
+
MongoDB service, so the parity tests actually run rather than skipping
|
|
349
|
+
themselves. The browser code is checked under node, and the package is
|
|
350
|
+
built, checked with `twine` and installed into a clean environment.
|
|
351
|
+
|
|
352
|
+
Releasing to PyPI is one tag away, once the repository is registered as a
|
|
353
|
+
trusted publisher on PyPI:
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
git tag v0.1.0 && git push origin v0.1.0
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## Where to take it next
|
|
362
|
+
|
|
363
|
+
The interesting problems in a database are all still open here:
|
|
364
|
+
|
|
365
|
+
1. **Descending index directions** -- a compound key whose components can
|
|
366
|
+
each be scanned backwards, and a planner that uses them for reverse sorts.
|
|
367
|
+
2. **A real storage engine** -- replace the SQLite file with a write-ahead
|
|
368
|
+
log, free-space reuse and page-level compaction.
|
|
369
|
+
3. **Concurrency** -- MVCC with snapshot isolation instead of a single
|
|
370
|
+
write lock.
|
|
371
|
+
4. **A server** -- a wire protocol and a client library, so minidb can be
|
|
372
|
+
used from another language.
|
|
373
|
+
5. **A better planner** -- cost estimates, index intersection, and choosing
|
|
374
|
+
between several usable indexes.
|
|
375
|
+
6. **Full-text search** -- an inverted index over tokenised documents.
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
## Layout
|
|
380
|
+
|
|
381
|
+
```
|
|
382
|
+
minidb/ the database
|
|
383
|
+
collection.py the API
|
|
384
|
+
query.py filter semantics
|
|
385
|
+
update.py update operators
|
|
386
|
+
aggregation.py the pipeline
|
|
387
|
+
validation.py schemas
|
|
388
|
+
index.py key encoding + planner
|
|
389
|
+
engine.py SQLite storage layer
|
|
390
|
+
comparison.py BSON value ordering
|
|
391
|
+
bson.py document codec
|
|
392
|
+
objectid.py ObjectId
|
|
393
|
+
database.py the Database object
|
|
394
|
+
demo.py sample data
|
|
395
|
+
webui/ the web UI (stdlib only)
|
|
396
|
+
server.py the HTTP API
|
|
397
|
+
static/ the browser app
|
|
398
|
+
schemas/ the example app's schemas
|
|
399
|
+
app/ the example app
|
|
400
|
+
bench/ benchmarks
|
|
401
|
+
docs/ the screenshot
|
|
402
|
+
tests/
|
|
403
|
+
test_minidb.py unit tests
|
|
404
|
+
test_parity.py minidb vs. real MongoDB
|
|
405
|
+
test_webui.py the web UI's API
|
|
406
|
+
frontend_smoke.mjs the browser code, in a DOM shim
|
|
407
|
+
```
|