minidb-sql 1.3.0__py3-none-any.whl

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.
@@ -0,0 +1,379 @@
1
+ Metadata-Version: 2.4
2
+ Name: minidb-sql
3
+ Version: 1.3.0
4
+ Summary: A miniature in-memory database with SQL-like query support
5
+ Author: MiniDB Contributors
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/sebtardif/MiniDB
8
+ Project-URL: Repository, https://github.com/sebtardif/MiniDB
9
+ Project-URL: Issues, https://github.com/sebtardif/MiniDB/issues
10
+ Project-URL: Changelog, https://github.com/sebtardif/MiniDB/blob/main/CHANGELOG.md
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Topic :: Database
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.11
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Dynamic: license-file
24
+
25
+ # MiniDB
26
+
27
+ [![CI](https://github.com/SebTardif/MiniDB/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/SebTardif/MiniDB/actions/workflows/ci.yml?query=branch%3Amain)
28
+ [![Security](https://github.com/SebTardif/MiniDB/actions/workflows/security.yml/badge.svg?event=push)](https://github.com/SebTardif/MiniDB/actions/workflows/security.yml)
29
+ [![Release](https://img.shields.io/github/v/release/SebTardif/MiniDB?logo=github&sort=semver)](https://github.com/SebTardif/MiniDB/releases/latest)
30
+ [![Python](https://img.shields.io/badge/python-3.11%2B-blue?logo=python&logoColor=white)](https://www.python.org/downloads/)
31
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
32
+
33
+ [![Tests](https://img.shields.io/badge/tests-200%2B%20passing-brightgreen)](https://github.com/SebTardif/MiniDB/tree/main/tests)
34
+ [![Scorecard](https://github.com/SebTardif/MiniDB/actions/workflows/scorecard.yml/badge.svg?event=push)](https://github.com/SebTardif/MiniDB/actions/workflows/scorecard.yml)
35
+
36
+ A miniature in-memory database with SQL-like query support, built from scratch using only Python 3.11+ standard library.
37
+
38
+ ## Features
39
+
40
+ - **Typed Columns**: INTEGER, STRING, FLOAT, BOOLEAN
41
+ - **CRUD Operations**: INSERT, SELECT, UPDATE, DELETE
42
+ - **Parameters**: `?` placeholders bound after parse (`execute(sql, params)`)
43
+ - **Transactions**: in-memory `BEGIN` / `COMMIT` / `ROLLBACK` snapshots
44
+ - **SQL-like Query Language**:
45
+ - WHERE with AND/OR, NOT, parentheses, comparisons (=, >, <, >=, <=, !=, LIKE, IN), and IS NULL / IS NOT NULL
46
+ - SELECT column aliases (`col AS alias`)
47
+ - SELECT DISTINCT
48
+ - ORDER BY (ASC/DESC)
49
+ - GROUP BY with aggregations
50
+ - HAVING after GROUP BY
51
+ - LIMIT clause
52
+ - Searched `CASE` in the select list (`CASE WHEN ... THEN ... ELSE ... END`)
53
+ - **Aggregations**: COUNT, SUM, AVG, MIN, MAX
54
+ - **JOINs**: INNER JOIN and LEFT JOIN between tables
55
+ - **Indexing**: Automatic hash-based indexing on primary keys
56
+ - **Query Planner**: Chooses between index scans and table scans (`db.explain(sql)`)
57
+ - **Persistence**: Save/load database to JSON files with versioning
58
+
59
+ ## Installation
60
+
61
+ ```bash
62
+ pip install minidb-sql
63
+ ```
64
+
65
+ The library uses only the Python standard library. `pip install minidb` is a different project.
66
+
67
+ ```python
68
+ from minidb_sql import MiniDB
69
+ ```
70
+
71
+ ## Quick Start
72
+
73
+ `execute()` runs any statement and returns a row id, an affected-row count, or None.
74
+ `query()` is SELECT-only and returns rows.
75
+ Both accept an optional `params` sequence for `?` placeholders. Values are bound
76
+ after parse, not interpolated into the SQL string.
77
+
78
+ ```python
79
+ from minidb_sql import MiniDB, Column, ColumnType
80
+
81
+ # Create database
82
+ db = MiniDB()
83
+
84
+ # Create table using SQL
85
+ db.execute("""
86
+ CREATE TABLE users (
87
+ id INTEGER PRIMARY KEY,
88
+ name STRING NOT NULL,
89
+ age INTEGER,
90
+ salary FLOAT,
91
+ active BOOLEAN
92
+ )
93
+ """)
94
+
95
+ # Or create table programmatically
96
+ db.create_table(
97
+ 'orders',
98
+ [
99
+ Column('id', ColumnType.INTEGER, primary_key=True),
100
+ Column('user_id', ColumnType.INTEGER),
101
+ Column('product', ColumnType.STRING),
102
+ Column('total', ColumnType.FLOAT),
103
+ ],
104
+ )
105
+
106
+ # Insert data
107
+ db.execute("INSERT INTO users (id, name, age, salary, active) VALUES (1, 'Alice', 30, 75000.0, true)")
108
+ db.execute(
109
+ 'INSERT INTO users (id, name, age, salary, active) VALUES (?, ?, ?, ?, ?)',
110
+ [2, 'Bob', 25, 55000.0, False],
111
+ )
112
+
113
+ # Query data
114
+ results = db.query('SELECT * FROM users WHERE age > ?', [28])
115
+
116
+ # Complex queries
117
+ results = db.query("""
118
+ SELECT name, salary FROM users
119
+ WHERE active = true AND salary > 60000
120
+ ORDER BY salary DESC
121
+ """)
122
+
123
+ # Inspect the SELECT plan (index_scan vs table_scan)
124
+ print(db.explain('SELECT * FROM users WHERE id = 1'))
125
+
126
+ # Aggregations
127
+ results = db.query('SELECT COUNT(*), AVG(salary) FROM users')
128
+
129
+ # GROUP BY with HAVING
130
+ results = db.query("""
131
+ SELECT active, COUNT(*), AVG(salary)
132
+ FROM users
133
+ GROUP BY active
134
+ HAVING COUNT(*) > 1
135
+ """)
136
+
137
+ # JOINs
138
+ results = db.query("""
139
+ SELECT users.name, orders.product, orders.total
140
+ FROM users
141
+ JOIN orders ON users.id = orders.user_id
142
+ WHERE orders.total > 50
143
+ """)
144
+
145
+ # UPDATE
146
+ affected = db.execute('UPDATE users SET salary = 80000.0 WHERE id = 1')
147
+
148
+ # DELETE
149
+ affected = db.execute('DELETE FROM users WHERE active = false')
150
+
151
+ # Snapshot transaction (in-memory only)
152
+ db.execute('BEGIN')
153
+ db.execute("INSERT INTO users (id, name, age, salary, active) VALUES (3, 'Cara', 40, 90000.0, true)")
154
+ db.execute('ROLLBACK')
155
+
156
+ # DROP TABLE (SQL or Python API)
157
+ db.execute('CREATE TABLE scratch (id INTEGER PRIMARY KEY)')
158
+ db.execute('DROP TABLE scratch')
159
+ db.create_table('temp', [Column('id', ColumnType.INTEGER, primary_key=True)])
160
+ db.drop_table('temp')
161
+
162
+ # Persistence
163
+ db.save('my_database.json')
164
+ db = MiniDB.load('my_database.json')
165
+ ```
166
+
167
+ ## SQL Syntax Reference
168
+
169
+ ### CREATE TABLE
170
+
171
+ ```sql
172
+ CREATE TABLE table_name (
173
+ column1 INTEGER PRIMARY KEY,
174
+ column2 STRING NOT NULL,
175
+ column3 FLOAT,
176
+ column4 BOOLEAN
177
+ )
178
+ ```
179
+
180
+ ### INSERT
181
+
182
+ ```sql
183
+ INSERT INTO table_name (col1, col2, col3) VALUES (1, 'value', 3.14)
184
+ INSERT INTO table_name (col1, col2, col3) VALUES (?, ?, ?)
185
+ ```
186
+
187
+ Use `db.execute(sql, [1, 'value', 3.14])` or `db.query(sql, params)` to bind `?`
188
+ left-to-right. Too few or too many params raise `InvalidQueryError`.
189
+
190
+ ### SELECT
191
+
192
+ ```sql
193
+ SELECT * FROM table_name
194
+ SELECT col1, col2 FROM table_name
195
+ SELECT col1 AS alias FROM table_name
196
+ SELECT DISTINCT col1 FROM table_name
197
+ SELECT DISTINCT col1, col2 FROM table_name
198
+ SELECT col1, COUNT(*), AVG(col2) FROM table_name GROUP BY col1
199
+ SELECT col1, COUNT(*) FROM table_name GROUP BY col1 HAVING COUNT(*) > 1
200
+ SELECT col1, COUNT(*) AS n FROM table_name GROUP BY col1 HAVING n > 1
201
+ ```
202
+
203
+ ### CASE
204
+
205
+ ```sql
206
+ SELECT CASE WHEN age >= 18 THEN 'adult' ELSE 'minor' END FROM users
207
+ SELECT CASE WHEN age >= 18 THEN 'adult' WHEN age IS NULL THEN 'unknown' ELSE 'minor' END AS kind
208
+ ```
209
+
210
+ `CASE` is searched form in the select list. The first true `WHEN` wins. A comparison that is unknown, including `= NULL`, is not true, so a later `WHEN` or `ELSE` runs. No `ELSE` returns NULL. `WHEN` uses the same comparison rules as `WHERE`. The result column is `CASE` unless you give it an alias. `CASE` next to `GROUP BY` may only read grouped columns.
211
+
212
+ ### HAVING
213
+
214
+ ```sql
215
+ HAVING COUNT(*) > 1
216
+ HAVING SUM(quantity) > 30
217
+ HAVING n > 2
218
+ ```
219
+
220
+ HAVING filters grouped rows after GROUP BY (or the single row from an ungrouped aggregate). It is rejected when the SELECT has neither GROUP BY nor aggregate functions.
221
+
222
+ ### WHERE Clause
223
+
224
+ ```sql
225
+ WHERE col = value
226
+ WHERE col > value
227
+ WHERE col >= value
228
+ WHERE col < value
229
+ WHERE col <= value
230
+ WHERE col != value
231
+ WHERE col LIKE 'pattern%' -- % matches any sequence
232
+ WHERE col IN (1, 2, 3)
233
+ WHERE col = ?
234
+ WHERE col IN (?, ?)
235
+ WHERE col IS NULL
236
+ WHERE col IS NOT NULL
237
+ WHERE cond1 AND cond2
238
+ WHERE cond1 OR cond2
239
+ WHERE NOT col = value
240
+ WHERE (cond1 OR cond2) AND cond3
241
+ ```
242
+
243
+ ### ORDER BY
244
+
245
+ ```sql
246
+ ORDER BY col ASC
247
+ ORDER BY col DESC
248
+ ORDER BY col1 ASC, col2 DESC
249
+ ```
250
+
251
+ ### LIMIT
252
+
253
+ ```sql
254
+ LIMIT 10
255
+ ```
256
+
257
+ ### JOIN
258
+
259
+ ```sql
260
+ SELECT t1.col, t2.col
261
+ FROM t1
262
+ JOIN t2 ON t1.id = t2.t1_id
263
+
264
+ -- LEFT JOIN (includes unmatched left rows with NULLs)
265
+ SELECT t1.col, t2.col
266
+ FROM t1
267
+ LEFT JOIN t2 ON t1.id = t2.t1_id
268
+ ```
269
+
270
+ ### UPDATE
271
+
272
+ ```sql
273
+ UPDATE table_name SET col1 = value1, col2 = value2 WHERE condition
274
+ ```
275
+
276
+ ### DELETE
277
+
278
+ ```sql
279
+ DELETE FROM table_name WHERE condition
280
+ ```
281
+
282
+ ### BEGIN / COMMIT / ROLLBACK
283
+
284
+ ```sql
285
+ BEGIN
286
+ COMMIT
287
+ ROLLBACK
288
+ ```
289
+
290
+ These copy the in-memory tables at `BEGIN` and restore that copy on `ROLLBACK`.
291
+ They are not durable and not concurrent. MiniDB is single-threaded.
292
+
293
+ ### DROP TABLE
294
+
295
+ ```sql
296
+ DROP TABLE table_name
297
+ ```
298
+
299
+ ```python
300
+ db.execute('DROP TABLE table_name')
301
+ db.drop_table('table_name')
302
+ ```
303
+
304
+ ## Architecture
305
+
306
+ ```
307
+ minidb_sql/
308
+ ├── __init__.py # Public API exports
309
+ ├── database.py # MiniDB main class
310
+ ├── table.py # Table storage
311
+ ├── column.py # Column and Schema definitions
312
+ ├── index.py # Hash-based indexing
313
+ ├── parser.py # SQL lexer and parser
314
+ ├── query.py # Query execution engine
315
+ ├── planner.py # Query planner
316
+ ├── persistence.py # JSON serialization
317
+ ├── types.py # Type definitions
318
+ └── errors.py # Custom exceptions
319
+ ```
320
+
321
+ ## Test Suite
322
+
323
+ MiniDB includes a comprehensive test suite with 95+ test cases:
324
+
325
+ ```bash
326
+ # Run all tests
327
+ python -m pytest tests/ -v
328
+
329
+ # Run specific test file
330
+ python -m pytest tests/test_queries.py -v
331
+
332
+ # Run with coverage
333
+ python -m pytest tests/ -v --cov=minidb_sql
334
+ ```
335
+
336
+ ### Test Categories
337
+
338
+ - **test_database.py**: Database lifecycle, table management, `?` parameters, and snapshot transactions
339
+ - **test_crud.py**: INSERT, SELECT, UPDATE, DELETE operations
340
+ - **test_queries.py**: WHERE, ORDER BY, LIMIT, parser errors, type validation
341
+ - **test_aggregations.py**: COUNT, SUM, AVG, MIN, MAX, GROUP BY, HAVING
342
+ - **test_joins.py**: JOIN operations
343
+ - **test_index.py**: Indexing and query planning
344
+ - **test_persistence.py**: Save/load functionality
345
+ - **test_performance.py**: Large dataset tests (10,000+ rows)
346
+
347
+ ## Performance
348
+
349
+ MiniDB is designed for small to medium datasets:
350
+
351
+ - **10,000 rows**: All operations complete in under 30 seconds
352
+ - **Indexed lookups**: O(1) for primary key equality
353
+ - **Range queries**: O(n) with index optimization
354
+ - **Table scans**: O(n) for non-indexed columns
355
+
356
+ ## Limitations
357
+
358
+ - In-memory only (no disk-based storage during operation)
359
+ - Single-threaded
360
+ - Transactions are in-memory snapshots only (not durable, not concurrent)
361
+ - No foreign key constraints
362
+ - Limited JOIN support (INNER and LEFT JOIN only, no RIGHT JOIN execution)
363
+
364
+ ## License
365
+
366
+ MIT License - Use freely for any purpose.
367
+
368
+ ## Contributing
369
+
370
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines.
371
+
372
+ Areas for improvement:
373
+
374
+ - B-tree indexes for range queries
375
+ - RIGHT JOIN
376
+ - Subqueries
377
+ - More aggregate functions
378
+ - Query optimization
379
+ - Concurrent access
@@ -0,0 +1,16 @@
1
+ minidb_sql/__init__.py,sha256=GuP1vlebl86YAseINj-zOe8OV2C3lWKemLZtq_9VOsk,3780
2
+ minidb_sql/column.py,sha256=IaFunqAf4t9VuBeUyqg7Ga42gVP_X8scMldwagkg-Qw,5892
3
+ minidb_sql/database.py,sha256=6FJaCNumKwhpvdH3d4W1xH6VJTuYqDiH52aATA_CVIE,7868
4
+ minidb_sql/errors.py,sha256=T7yOhaQlAqPNLrIYKzZaZgOusGIBqy-T0oXc7iZstm4,4817
5
+ minidb_sql/index.py,sha256=K6xNGyszpNdEaC7JBmsJwfG1-HrYhsZgQtHiFiivQJs,7597
6
+ minidb_sql/parser.py,sha256=PyxR9idz1mJLK6S8b-kX_xMvbZ4Z1KgQiDmB82cj7EA,41193
7
+ minidb_sql/persistence.py,sha256=duwO83p5BNtWTVkk9-RP65O52hFVECH71Rqz1EZlLbI,2159
8
+ minidb_sql/planner.py,sha256=Lzhd_y5dnRyrKVLrGfQR527PRCUKewnixPRBM4gZc6k,5469
9
+ minidb_sql/query.py,sha256=rsMksnFwCftx9xn3b6msyKjSxLOegDsUFsJY9VYdkZ8,46554
10
+ minidb_sql/table.py,sha256=Q1g_aFIaWKQhwId1OctWGMEtRaYQI4aXrxV3KTnZDPA,9294
11
+ minidb_sql/types.py,sha256=oQAgyQX_uGnTvsZqUgAh-PURBLPeOJIRgeKSx4x8eLM,5099
12
+ minidb_sql-1.3.0.dist-info/licenses/LICENSE,sha256=457P-Q6b8q8CuJhk5SkOeqPvwlzrHYXA4lePbJeOktE,1073
13
+ minidb_sql-1.3.0.dist-info/METADATA,sha256=bIYzmYPlyqJJvT9IFLCJuwW4_304QOpPVmowLKd_E6U,10873
14
+ minidb_sql-1.3.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
15
+ minidb_sql-1.3.0.dist-info/top_level.txt,sha256=Ti3AuUr86Ce5BHEh7KCs9Cx313q1oJ4u4HkzicXmiPc,11
16
+ minidb_sql-1.3.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Sebastien Tardif
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.
@@ -0,0 +1 @@
1
+ minidb_sql