cinchdb 0.1.10__tar.gz → 0.1.11__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.
- {cinchdb-0.1.10 → cinchdb-0.1.11}/PKG-INFO +5 -36
- {cinchdb-0.1.10 → cinchdb-0.1.11}/README.md +4 -35
- {cinchdb-0.1.10 → cinchdb-0.1.11}/pyproject.toml +1 -1
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/cli/commands/column.py +3 -4
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/cli/commands/database.py +58 -60
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/cli/commands/table.py +3 -3
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/cli/main.py +1 -7
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/cli/utils.py +23 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/core/database.py +138 -11
- cinchdb-0.1.11/src/cinchdb/core/initializer.py +392 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/core/path_utils.py +44 -27
- cinchdb-0.1.11/src/cinchdb/infrastructure/metadata_connection_pool.py +145 -0
- cinchdb-0.1.11/src/cinchdb/infrastructure/metadata_db.py +376 -0
- cinchdb-0.1.11/src/cinchdb/managers/branch.py +266 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/managers/change_applier.py +30 -13
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/managers/column.py +4 -10
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/managers/query.py +40 -4
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/managers/table.py +8 -6
- cinchdb-0.1.11/src/cinchdb/managers/tenant.py +901 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/models/table.py +0 -4
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/models/tenant.py +4 -2
- cinchdb-0.1.10/src/cinchdb/core/initializer.py +0 -214
- cinchdb-0.1.10/src/cinchdb/managers/branch.py +0 -170
- cinchdb-0.1.10/src/cinchdb/managers/tenant.py +0 -370
- {cinchdb-0.1.10 → cinchdb-0.1.11}/.gitignore +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/LICENSE +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/__init__.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/__main__.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/cli/__init__.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/cli/commands/__init__.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/cli/commands/branch.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/cli/commands/codegen.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/cli/commands/index.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/cli/commands/query.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/cli/commands/remote.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/cli/commands/tenant.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/cli/commands/view.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/cli/handlers/__init__.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/cli/handlers/codegen_handler.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/config.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/core/__init__.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/core/connection.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/core/maintenance.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/managers/__init__.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/managers/change_comparator.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/managers/change_tracker.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/managers/codegen.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/managers/data.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/managers/index.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/managers/merge_manager.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/managers/view.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/models/__init__.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/models/base.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/models/branch.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/models/change.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/models/database.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/models/project.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/models/view.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/utils/__init__.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/utils/name_validator.py +0 -0
- {cinchdb-0.1.10 → cinchdb-0.1.11}/src/cinchdb/utils/sql_validator.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: cinchdb
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.11
|
4
4
|
Summary: A Git-like SQLite database management system with branching and multi-tenancy
|
5
5
|
Project-URL: Homepage, https://github.com/russellromney/cinchdb
|
6
6
|
Project-URL: Documentation, https://russellromney.github.io/cinchdb
|
@@ -58,10 +58,7 @@ cinch branch merge-into-main feature
|
|
58
58
|
cinch tenant create customer_a
|
59
59
|
cinch query "SELECT * FROM users" --tenant customer_a
|
60
60
|
|
61
|
-
#
|
62
|
-
# Connect to remote CinchDB instance
|
63
|
-
cinch remote add production https://your-cinchdb-server.com your-api-key
|
64
|
-
cinch remote use production
|
61
|
+
# Future: Remote connectivity planned for production deployment
|
65
62
|
|
66
63
|
# Autogenerate Python SDK from database
|
67
64
|
cinch codegen generate python cinchdb_models/
|
@@ -75,9 +72,7 @@ CinchDB combines SQLite with Git-like workflows for database schema management:
|
|
75
72
|
- **Multi-tenant isolation** - shared schema, isolated data per tenant
|
76
73
|
- **Automatic change tracking** - all schema changes tracked and mergeable
|
77
74
|
- **Safe structure changes** - change merges happen atomically with zero rollback risk (seriously)
|
78
|
-
- **
|
79
|
-
- **Type-safe SDK** - Python and TypeScript SDKs with full type safety
|
80
|
-
- **Remote-capable** - coming soon - CLI and SDK can connect to remote instances
|
75
|
+
- **Type-safe Python SDK** - Python SDK with full type safety
|
81
76
|
- **SDK generation from database schema** - Generate a typesafe SDK from your database models for CRUD operations
|
82
77
|
|
83
78
|
## Installation
|
@@ -149,37 +144,11 @@ results = db.insert("posts", *post_list)
|
|
149
144
|
db.update("posts", post_id, {"content": "Updated content"})
|
150
145
|
```
|
151
146
|
|
152
|
-
### Remote Connection
|
153
|
-
|
154
|
-
Coming soon.
|
155
|
-
|
156
|
-
```python
|
157
|
-
# Connect to remote instance
|
158
|
-
db = cinchdb.connect("myapp", url="https://your-cinchdb-server.com", api_key="your-api-key")
|
159
|
-
|
160
|
-
# Same interface as local
|
161
|
-
results = db.query("SELECT * FROM users")
|
162
|
-
user_id = db.insert("users", {"username": "alice", "email": "alice@example.com"})
|
163
|
-
```
|
164
|
-
|
165
|
-
## Remote Access - coming soon
|
166
|
-
|
167
|
-
Connect to a remote CinchDB instance:
|
168
|
-
|
169
|
-
```bash
|
170
|
-
cinch remote add production https://your-cinchdb-server.com your-api-key
|
171
|
-
cinch remote use production
|
172
|
-
# Now all commands will use the remote instance
|
173
|
-
```
|
174
|
-
|
175
|
-
Interactive docs at `/docs`, health check at `/health`.
|
176
147
|
|
177
148
|
## Architecture
|
178
149
|
|
179
|
-
- **Python SDK**: Core functionality
|
180
|
-
- **CLI**: Full-featured command-line interface
|
181
|
-
- **Remote Access**: Connect to hosted CinchDB instances
|
182
|
-
- **TypeScript SDK**: Browser and Node.js client
|
150
|
+
- **Python SDK**: Core functionality for local development
|
151
|
+
- **CLI**: Full-featured command-line interface
|
183
152
|
|
184
153
|
## Development
|
185
154
|
|
@@ -31,10 +31,7 @@ cinch branch merge-into-main feature
|
|
31
31
|
cinch tenant create customer_a
|
32
32
|
cinch query "SELECT * FROM users" --tenant customer_a
|
33
33
|
|
34
|
-
#
|
35
|
-
# Connect to remote CinchDB instance
|
36
|
-
cinch remote add production https://your-cinchdb-server.com your-api-key
|
37
|
-
cinch remote use production
|
34
|
+
# Future: Remote connectivity planned for production deployment
|
38
35
|
|
39
36
|
# Autogenerate Python SDK from database
|
40
37
|
cinch codegen generate python cinchdb_models/
|
@@ -48,9 +45,7 @@ CinchDB combines SQLite with Git-like workflows for database schema management:
|
|
48
45
|
- **Multi-tenant isolation** - shared schema, isolated data per tenant
|
49
46
|
- **Automatic change tracking** - all schema changes tracked and mergeable
|
50
47
|
- **Safe structure changes** - change merges happen atomically with zero rollback risk (seriously)
|
51
|
-
- **
|
52
|
-
- **Type-safe SDK** - Python and TypeScript SDKs with full type safety
|
53
|
-
- **Remote-capable** - coming soon - CLI and SDK can connect to remote instances
|
48
|
+
- **Type-safe Python SDK** - Python SDK with full type safety
|
54
49
|
- **SDK generation from database schema** - Generate a typesafe SDK from your database models for CRUD operations
|
55
50
|
|
56
51
|
## Installation
|
@@ -122,37 +117,11 @@ results = db.insert("posts", *post_list)
|
|
122
117
|
db.update("posts", post_id, {"content": "Updated content"})
|
123
118
|
```
|
124
119
|
|
125
|
-
### Remote Connection
|
126
|
-
|
127
|
-
Coming soon.
|
128
|
-
|
129
|
-
```python
|
130
|
-
# Connect to remote instance
|
131
|
-
db = cinchdb.connect("myapp", url="https://your-cinchdb-server.com", api_key="your-api-key")
|
132
|
-
|
133
|
-
# Same interface as local
|
134
|
-
results = db.query("SELECT * FROM users")
|
135
|
-
user_id = db.insert("users", {"username": "alice", "email": "alice@example.com"})
|
136
|
-
```
|
137
|
-
|
138
|
-
## Remote Access - coming soon
|
139
|
-
|
140
|
-
Connect to a remote CinchDB instance:
|
141
|
-
|
142
|
-
```bash
|
143
|
-
cinch remote add production https://your-cinchdb-server.com your-api-key
|
144
|
-
cinch remote use production
|
145
|
-
# Now all commands will use the remote instance
|
146
|
-
```
|
147
|
-
|
148
|
-
Interactive docs at `/docs`, health check at `/health`.
|
149
120
|
|
150
121
|
## Architecture
|
151
122
|
|
152
|
-
- **Python SDK**: Core functionality
|
153
|
-
- **CLI**: Full-featured command-line interface
|
154
|
-
- **Remote Access**: Connect to hosted CinchDB instances
|
155
|
-
- **TypeScript SDK**: Browser and Node.js client
|
123
|
+
- **Python SDK**: Core functionality for local development
|
124
|
+
- **CLI**: Full-featured command-line interface
|
156
125
|
|
157
126
|
## Development
|
158
127
|
|
@@ -41,14 +41,14 @@ def list_columns(
|
|
41
41
|
col_table.add_column("Name", style="cyan")
|
42
42
|
col_table.add_column("Type", style="green")
|
43
43
|
col_table.add_column("Nullable", style="yellow")
|
44
|
-
col_table.add_column("
|
44
|
+
col_table.add_column("Unique", style="red")
|
45
45
|
col_table.add_column("Default", style="blue")
|
46
46
|
|
47
47
|
for col in columns:
|
48
48
|
nullable = "Yes" if col.nullable else "No"
|
49
|
-
|
49
|
+
unique = "Yes" if col.unique else "No"
|
50
50
|
default = col.default or "-"
|
51
|
-
col_table.add_row(col.name, col.type, nullable,
|
51
|
+
col_table.add_row(col.name, col.type, nullable, unique, default)
|
52
52
|
|
53
53
|
console.print(col_table)
|
54
54
|
|
@@ -217,7 +217,6 @@ def info(
|
|
217
217
|
console.print(f"Table: {table}")
|
218
218
|
console.print(f"Type: {column.type}")
|
219
219
|
console.print(f"Nullable: {'Yes' if column.nullable else 'No'}")
|
220
|
-
console.print(f"Primary Key: {'Yes' if column.primary_key else 'No'}")
|
221
220
|
console.print(f"Unique: {'Yes' if column.unique else 'No'}")
|
222
221
|
console.print(f"Default: {column.default or 'None'}")
|
223
222
|
|
@@ -75,43 +75,18 @@ def create(
|
|
75
75
|
|
76
76
|
config, config_data = get_config_with_data()
|
77
77
|
|
78
|
-
#
|
79
|
-
|
80
|
-
|
78
|
+
# Use the ProjectInitializer to create the database properly
|
79
|
+
from cinchdb.core.initializer import ProjectInitializer
|
80
|
+
|
81
|
+
initializer = ProjectInitializer(config.project_dir)
|
82
|
+
|
83
|
+
try:
|
84
|
+
# Create database using initializer (lazy by default)
|
85
|
+
initializer.init_database(name, description=description, lazy=True)
|
86
|
+
except FileExistsError:
|
81
87
|
console.print(f"[red]❌ Database '{name}' already exists[/red]")
|
82
88
|
raise typer.Exit(1)
|
83
89
|
|
84
|
-
# Create the database structure
|
85
|
-
db_path.mkdir(parents=True)
|
86
|
-
branches_dir = db_path / "branches"
|
87
|
-
branches_dir.mkdir()
|
88
|
-
|
89
|
-
# Create main branch
|
90
|
-
main_branch = branches_dir / "main"
|
91
|
-
main_branch.mkdir()
|
92
|
-
|
93
|
-
# Create main tenant
|
94
|
-
tenants_dir = main_branch / "tenants"
|
95
|
-
tenants_dir.mkdir()
|
96
|
-
main_tenant = tenants_dir / "main.db"
|
97
|
-
main_tenant.touch()
|
98
|
-
|
99
|
-
# Create branch metadata
|
100
|
-
import json
|
101
|
-
from datetime import datetime, timezone
|
102
|
-
|
103
|
-
metadata = {
|
104
|
-
"name": "main",
|
105
|
-
"parent": None,
|
106
|
-
"created_at": datetime.now(timezone.utc).isoformat(),
|
107
|
-
}
|
108
|
-
with open(main_branch / "metadata.json", "w") as f:
|
109
|
-
json.dump(metadata, f, indent=2)
|
110
|
-
|
111
|
-
# Create empty changes file
|
112
|
-
with open(main_branch / "changes.json", "w") as f:
|
113
|
-
json.dump([], f)
|
114
|
-
|
115
90
|
console.print(f"[green]✅ Created database '{name}'[/green]")
|
116
91
|
|
117
92
|
if switch:
|
@@ -134,11 +109,15 @@ def delete(
|
|
134
109
|
raise typer.Exit(1)
|
135
110
|
|
136
111
|
config, config_data = get_config_with_data()
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
112
|
+
|
113
|
+
# Check if database exists in metadata
|
114
|
+
from cinchdb.infrastructure.metadata_db import MetadataDB
|
115
|
+
|
116
|
+
with MetadataDB(config.project_dir) as metadata_db:
|
117
|
+
db_info = metadata_db.get_database(name)
|
118
|
+
if not db_info:
|
119
|
+
console.print(f"[red]❌ Database '{name}' does not exist[/red]")
|
120
|
+
raise typer.Exit(1)
|
142
121
|
|
143
122
|
# Confirmation
|
144
123
|
if not force:
|
@@ -147,10 +126,17 @@ def delete(
|
|
147
126
|
console.print("[yellow]Cancelled[/yellow]")
|
148
127
|
raise typer.Exit(0)
|
149
128
|
|
150
|
-
# Delete the database
|
129
|
+
# Delete the database from metadata and filesystem if materialized
|
151
130
|
import shutil
|
152
|
-
|
153
|
-
|
131
|
+
|
132
|
+
with MetadataDB(config.project_dir) as metadata_db:
|
133
|
+
# Delete from metadata
|
134
|
+
metadata_db.delete_database(db_info['id'])
|
135
|
+
|
136
|
+
# Delete physical files if they exist
|
137
|
+
db_path = config.project_dir / ".cinchdb" / "databases" / name
|
138
|
+
if db_path.exists():
|
139
|
+
shutil.rmtree(db_path)
|
154
140
|
|
155
141
|
# If this was the active database, switch to main
|
156
142
|
if config_data.active_database == name:
|
@@ -168,31 +154,40 @@ def info(
|
|
168
154
|
config, config_data = get_config_with_data()
|
169
155
|
db_name = name or config_data.active_database
|
170
156
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
157
|
+
# Check if database exists in metadata
|
158
|
+
from cinchdb.infrastructure.metadata_db import MetadataDB
|
159
|
+
|
160
|
+
with MetadataDB(config.project_dir) as metadata_db:
|
161
|
+
db_info = metadata_db.get_database(db_name)
|
162
|
+
if not db_info:
|
163
|
+
console.print(f"[red]❌ Database '{db_name}' does not exist[/red]")
|
164
|
+
raise typer.Exit(1)
|
165
|
+
|
166
|
+
# Get branch info from metadata
|
167
|
+
branches = metadata_db.list_branches(db_info['id'])
|
168
|
+
branch_count = len(branches)
|
179
169
|
|
180
170
|
# Get active branch
|
181
171
|
active_branch = config_data.active_branch
|
182
|
-
|
172
|
+
|
183
173
|
# Display info
|
184
174
|
console.print(f"\n[bold]Database: {db_name}[/bold]")
|
185
|
-
console.print(f"
|
175
|
+
console.print(f"Status: {'Materialized' if db_info.get('materialized') else 'Lazy (not materialized)'}")
|
186
176
|
console.print(f"Branches: {branch_count}")
|
187
177
|
console.print(f"Active Branch: {active_branch}")
|
188
178
|
console.print(f"Protected: {'Yes' if db_name == 'main' else 'No'}")
|
179
|
+
|
180
|
+
# Show description if present
|
181
|
+
if db_info.get('description'):
|
182
|
+
console.print(f"Description: {db_info['description']}")
|
189
183
|
|
190
184
|
# List branches
|
191
185
|
if branch_count > 0:
|
192
186
|
console.print("\n[bold]Branches:[/bold]")
|
193
|
-
|
194
|
-
|
195
|
-
|
187
|
+
with MetadataDB(config.project_dir) as metadata_db:
|
188
|
+
branches = metadata_db.list_branches(db_info['id'])
|
189
|
+
for branch in sorted(branches, key=lambda x: x['name']):
|
190
|
+
branch_name = branch['name']
|
196
191
|
is_active = " (active)" if branch_name == active_branch else ""
|
197
192
|
console.print(f" - {branch_name}{is_active}")
|
198
193
|
|
@@ -208,11 +203,14 @@ def switch(
|
|
208
203
|
name = validate_required_arg(name, "name", ctx)
|
209
204
|
config, config_data = get_config_with_data()
|
210
205
|
|
211
|
-
# Check if database exists
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
206
|
+
# Check if database exists in metadata
|
207
|
+
from cinchdb.infrastructure.metadata_db import MetadataDB
|
208
|
+
|
209
|
+
with MetadataDB(config.project_dir) as metadata_db:
|
210
|
+
db_info = metadata_db.get_database(name)
|
211
|
+
if not db_info:
|
212
|
+
console.print(f"[red]❌ Database '{name}' does not exist[/red]")
|
213
|
+
raise typer.Exit(1)
|
216
214
|
|
217
215
|
# Switch
|
218
216
|
set_active_database(config, name)
|
@@ -277,14 +277,14 @@ def info(
|
|
277
277
|
col_table.add_column("Name", style="cyan")
|
278
278
|
col_table.add_column("Type", style="green")
|
279
279
|
col_table.add_column("Nullable", style="yellow")
|
280
|
-
col_table.add_column("
|
280
|
+
col_table.add_column("Unique", style="red")
|
281
281
|
col_table.add_column("Default", style="blue")
|
282
282
|
|
283
283
|
for col in table.columns:
|
284
284
|
nullable = "Yes" if col.nullable else "No"
|
285
|
-
|
285
|
+
unique = "Yes" if col.unique else "No"
|
286
286
|
default = col.default or "-"
|
287
|
-
col_table.add_row(col.name, col.type, nullable,
|
287
|
+
col_table.add_row(col.name, col.type, nullable, unique, default)
|
288
288
|
|
289
289
|
console.print(col_table)
|
290
290
|
|
@@ -13,7 +13,6 @@ from cinchdb.cli.commands import (
|
|
13
13
|
column,
|
14
14
|
view,
|
15
15
|
codegen,
|
16
|
-
remote,
|
17
16
|
index,
|
18
17
|
)
|
19
18
|
|
@@ -45,7 +44,6 @@ app.add_typer(column.app, name="column", help="Column management commands")
|
|
45
44
|
app.add_typer(view.app, name="view", help="View management commands")
|
46
45
|
app.add_typer(index.app, name="index", help="Index management commands")
|
47
46
|
app.add_typer(codegen.app, name="codegen", help="Code generation commands")
|
48
|
-
app.add_typer(remote.app, name="remote", help="Remote instance management")
|
49
47
|
|
50
48
|
|
51
49
|
# Add query as direct command instead of subtyper
|
@@ -59,15 +57,11 @@ def query(
|
|
59
57
|
limit: Optional[int] = typer.Option(
|
60
58
|
None, "--limit", "-l", help="Limit number of rows"
|
61
59
|
),
|
62
|
-
local: bool = typer.Option(False, "--local", "-L", help="Force local connection"),
|
63
|
-
remote: Optional[str] = typer.Option(
|
64
|
-
None, "--remote", "-r", help="Use specific remote alias"
|
65
|
-
),
|
66
60
|
):
|
67
61
|
"""Execute a SQL query."""
|
68
62
|
from cinchdb.cli.commands.query import execute_query
|
69
63
|
|
70
|
-
execute_query(sql, tenant, format, limit
|
64
|
+
execute_query(sql, tenant, format, limit)
|
71
65
|
|
72
66
|
|
73
67
|
@app.command()
|
@@ -13,6 +13,29 @@ from cinchdb.core.database import CinchDB
|
|
13
13
|
console = Console()
|
14
14
|
|
15
15
|
|
16
|
+
def handle_cli_error(func):
|
17
|
+
"""Decorator to handle CLI errors with consistent formatting.
|
18
|
+
|
19
|
+
Args:
|
20
|
+
func: The function to wrap
|
21
|
+
"""
|
22
|
+
def wrapper(*args, **kwargs):
|
23
|
+
try:
|
24
|
+
return func(*args, **kwargs)
|
25
|
+
except Exception as error:
|
26
|
+
error_msg = str(error)
|
27
|
+
if "not found" in error_msg.lower():
|
28
|
+
console.print(f"[yellow]⚠️ {error_msg}[/yellow]")
|
29
|
+
elif "already exists" in error_msg.lower():
|
30
|
+
console.print(f"[yellow]⚠️ {error_msg}[/yellow]")
|
31
|
+
elif "invalid" in error_msg.lower():
|
32
|
+
console.print(f"[red]❌ {error_msg}[/red]")
|
33
|
+
else:
|
34
|
+
console.print(f"[red]❌ Error: {error_msg}[/red]")
|
35
|
+
raise typer.Exit(1)
|
36
|
+
return wrapper
|
37
|
+
|
38
|
+
|
16
39
|
def get_config_with_data():
|
17
40
|
"""Get config and load data from current directory.
|
18
41
|
|
@@ -86,6 +86,9 @@ class CinchDB:
|
|
86
86
|
self.api_url = None
|
87
87
|
self.api_key = None
|
88
88
|
self.is_local = True
|
89
|
+
|
90
|
+
# Auto-materialize lazy database if needed
|
91
|
+
self._materialize_database_if_lazy()
|
89
92
|
elif api_url is not None and api_key is not None:
|
90
93
|
# Remote connection
|
91
94
|
self.project_dir = None
|
@@ -111,6 +114,23 @@ class CinchDB:
|
|
111
114
|
self._merge_manager: Optional["MergeManager"] = None
|
112
115
|
self._index_manager: Optional["IndexManager"] = None
|
113
116
|
|
117
|
+
def _materialize_database_if_lazy(self) -> None:
|
118
|
+
"""Auto-materialize a lazy database if accessing it."""
|
119
|
+
if not self.is_local:
|
120
|
+
return
|
121
|
+
|
122
|
+
# Check if this is a lazy database using metadata DB
|
123
|
+
from cinchdb.infrastructure.metadata_db import MetadataDB
|
124
|
+
|
125
|
+
with MetadataDB(self.project_dir) as metadata_db:
|
126
|
+
db_info = metadata_db.get_database(self.database)
|
127
|
+
|
128
|
+
if db_info and not db_info['materialized']:
|
129
|
+
# Database exists in metadata but not materialized
|
130
|
+
from cinchdb.core.initializer import ProjectInitializer
|
131
|
+
initializer = ProjectInitializer(self.project_dir)
|
132
|
+
initializer.materialize_database(self.database)
|
133
|
+
|
114
134
|
@property
|
115
135
|
def session(self):
|
116
136
|
"""Get or create HTTP session for remote connections."""
|
@@ -561,6 +581,122 @@ class CinchDB:
|
|
561
581
|
changes.append(Change(**data))
|
562
582
|
return changes
|
563
583
|
|
584
|
+
def optimize_tenant(self, tenant_name: str = None, force: bool = False) -> bool:
|
585
|
+
"""Optimize a tenant's storage with VACUUM and page size adjustment.
|
586
|
+
|
587
|
+
Args:
|
588
|
+
tenant_name: Name of the tenant to optimize (default: current tenant)
|
589
|
+
force: If True, always perform optimization
|
590
|
+
|
591
|
+
Returns:
|
592
|
+
True if optimization was performed, False otherwise
|
593
|
+
|
594
|
+
Examples:
|
595
|
+
# Optimize current tenant
|
596
|
+
db.optimize_tenant()
|
597
|
+
|
598
|
+
# Optimize specific tenant
|
599
|
+
db.optimize_tenant("store_west")
|
600
|
+
|
601
|
+
# Force optimization even if not needed
|
602
|
+
db.optimize_tenant(force=True)
|
603
|
+
"""
|
604
|
+
if self.is_local:
|
605
|
+
tenant_to_optimize = tenant_name or self.tenant
|
606
|
+
return self.tenants.optimize_tenant_storage(tenant_to_optimize, force=force)
|
607
|
+
else:
|
608
|
+
raise NotImplementedError("Remote tenant optimization not implemented")
|
609
|
+
|
610
|
+
def get_tenant_size(self, tenant_name: str = None) -> dict:
|
611
|
+
"""Get storage size information for a tenant.
|
612
|
+
|
613
|
+
Args:
|
614
|
+
tenant_name: Name of tenant (default: current tenant)
|
615
|
+
|
616
|
+
Returns:
|
617
|
+
Dictionary with size information:
|
618
|
+
- name: Tenant name
|
619
|
+
- materialized: Whether tenant is materialized
|
620
|
+
- size_bytes: Size in bytes (0 if lazy)
|
621
|
+
- size_kb: Size in KB
|
622
|
+
- size_mb: Size in MB
|
623
|
+
- page_size: SQLite page size (if materialized)
|
624
|
+
- page_count: Number of pages (if materialized)
|
625
|
+
|
626
|
+
Examples:
|
627
|
+
# Get size of current tenant
|
628
|
+
size = db.get_tenant_size()
|
629
|
+
print(f"Current tenant uses {size['size_mb']:.2f} MB")
|
630
|
+
|
631
|
+
# Get size of specific tenant
|
632
|
+
size = db.get_tenant_size("store_west")
|
633
|
+
if size['materialized']:
|
634
|
+
print(f"Page size: {size['page_size']} bytes")
|
635
|
+
"""
|
636
|
+
if self.is_local:
|
637
|
+
tenant_to_check = tenant_name or self.tenant
|
638
|
+
return self.tenants.get_tenant_size(tenant_to_check)
|
639
|
+
else:
|
640
|
+
raise NotImplementedError("Remote tenant size query not implemented")
|
641
|
+
|
642
|
+
def get_storage_info(self) -> dict:
|
643
|
+
"""Get storage size information for all tenants in current branch.
|
644
|
+
|
645
|
+
Returns:
|
646
|
+
Dictionary with:
|
647
|
+
- tenants: List of individual tenant size info (sorted by size)
|
648
|
+
- total_size_bytes: Total size of all materialized tenants
|
649
|
+
- total_size_mb: Total size in MB
|
650
|
+
- lazy_count: Number of lazy tenants
|
651
|
+
- materialized_count: Number of materialized tenants
|
652
|
+
|
653
|
+
Examples:
|
654
|
+
# Get storage overview
|
655
|
+
info = db.get_storage_info()
|
656
|
+
print(f"Total storage: {info['total_size_mb']:.2f} MB")
|
657
|
+
print(f"Materialized tenants: {info['materialized_count']}")
|
658
|
+
print(f"Lazy tenants: {info['lazy_count']}")
|
659
|
+
|
660
|
+
# List largest tenants
|
661
|
+
for tenant in info['tenants'][:5]: # Top 5
|
662
|
+
if tenant['materialized']:
|
663
|
+
print(f"{tenant['name']}: {tenant['size_mb']:.2f} MB")
|
664
|
+
"""
|
665
|
+
if self.is_local:
|
666
|
+
return self.tenants.get_all_tenant_sizes()
|
667
|
+
else:
|
668
|
+
raise NotImplementedError("Remote storage info not implemented")
|
669
|
+
|
670
|
+
def optimize_all_tenants(self, force: bool = False) -> dict:
|
671
|
+
"""Optimize storage for all tenants in current branch.
|
672
|
+
|
673
|
+
This is designed to be called periodically to:
|
674
|
+
- Reclaim unused space with VACUUM
|
675
|
+
- Adjust page sizes as databases grow
|
676
|
+
- Keep small databases compact
|
677
|
+
|
678
|
+
Args:
|
679
|
+
force: If True, optimize all tenants regardless of size
|
680
|
+
|
681
|
+
Returns:
|
682
|
+
Dictionary with optimization results:
|
683
|
+
- optimized: List of tenant names that were optimized
|
684
|
+
- skipped: List of tenant names that were skipped
|
685
|
+
- errors: List of tuples (tenant_name, error_message)
|
686
|
+
|
687
|
+
Examples:
|
688
|
+
# Run periodic optimization
|
689
|
+
results = db.optimize_all_tenants()
|
690
|
+
print(f"Optimized {len(results['optimized'])} tenants")
|
691
|
+
|
692
|
+
# Force optimization of all tenants
|
693
|
+
results = db.optimize_all_tenants(force=True)
|
694
|
+
"""
|
695
|
+
if self.is_local:
|
696
|
+
return self.tenants.optimize_all_tenants(force=force)
|
697
|
+
else:
|
698
|
+
raise NotImplementedError("Remote tenant optimization not implemented")
|
699
|
+
|
564
700
|
def close(self):
|
565
701
|
"""Close any open connections."""
|
566
702
|
if not self.is_local and self._session:
|
@@ -632,17 +768,6 @@ def connect_api(
|
|
632
768
|
|
633
769
|
Returns:
|
634
770
|
CinchDB connection instance for remote API
|
635
|
-
|
636
|
-
Examples:
|
637
|
-
# Connect to remote API
|
638
|
-
db = connect_api("https://api.example.com", "your-api-key", "mydb")
|
639
|
-
|
640
|
-
# Connect to specific branch
|
641
|
-
db = connect_api("https://api.example.com", "your-api-key", "mydb", "dev")
|
642
|
-
|
643
|
-
# Use with context manager
|
644
|
-
with connect_api("https://api.example.com", "key", "mydb") as db:
|
645
|
-
results = db.query("SELECT * FROM users")
|
646
771
|
"""
|
647
772
|
return CinchDB(
|
648
773
|
database=database,
|
@@ -651,3 +776,5 @@ def connect_api(
|
|
651
776
|
api_url=api_url,
|
652
777
|
api_key=api_key,
|
653
778
|
)
|
779
|
+
|
780
|
+
|