cinchdb 0.1.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.
- cinchdb/__init__.py +7 -0
- cinchdb/__main__.py +6 -0
- cinchdb/api/__init__.py +5 -0
- cinchdb/api/app.py +76 -0
- cinchdb/api/auth.py +290 -0
- cinchdb/api/main.py +137 -0
- cinchdb/api/routers/__init__.py +25 -0
- cinchdb/api/routers/auth.py +135 -0
- cinchdb/api/routers/branches.py +368 -0
- cinchdb/api/routers/codegen.py +164 -0
- cinchdb/api/routers/columns.py +290 -0
- cinchdb/api/routers/data.py +479 -0
- cinchdb/api/routers/databases.py +177 -0
- cinchdb/api/routers/projects.py +133 -0
- cinchdb/api/routers/query.py +156 -0
- cinchdb/api/routers/tables.py +349 -0
- cinchdb/api/routers/tenants.py +216 -0
- cinchdb/api/routers/views.py +219 -0
- cinchdb/cli/__init__.py +0 -0
- cinchdb/cli/commands/__init__.py +1 -0
- cinchdb/cli/commands/branch.py +479 -0
- cinchdb/cli/commands/codegen.py +176 -0
- cinchdb/cli/commands/column.py +308 -0
- cinchdb/cli/commands/database.py +212 -0
- cinchdb/cli/commands/query.py +136 -0
- cinchdb/cli/commands/remote.py +144 -0
- cinchdb/cli/commands/table.py +289 -0
- cinchdb/cli/commands/tenant.py +173 -0
- cinchdb/cli/commands/view.py +189 -0
- cinchdb/cli/handlers/__init__.py +5 -0
- cinchdb/cli/handlers/codegen_handler.py +189 -0
- cinchdb/cli/main.py +137 -0
- cinchdb/cli/utils.py +182 -0
- cinchdb/config.py +177 -0
- cinchdb/core/__init__.py +5 -0
- cinchdb/core/connection.py +175 -0
- cinchdb/core/database.py +537 -0
- cinchdb/core/maintenance.py +73 -0
- cinchdb/core/path_utils.py +153 -0
- cinchdb/managers/__init__.py +26 -0
- cinchdb/managers/branch.py +167 -0
- cinchdb/managers/change_applier.py +414 -0
- cinchdb/managers/change_comparator.py +194 -0
- cinchdb/managers/change_tracker.py +182 -0
- cinchdb/managers/codegen.py +523 -0
- cinchdb/managers/column.py +579 -0
- cinchdb/managers/data.py +455 -0
- cinchdb/managers/merge_manager.py +429 -0
- cinchdb/managers/query.py +214 -0
- cinchdb/managers/table.py +383 -0
- cinchdb/managers/tenant.py +258 -0
- cinchdb/managers/view.py +252 -0
- cinchdb/models/__init__.py +27 -0
- cinchdb/models/base.py +44 -0
- cinchdb/models/branch.py +26 -0
- cinchdb/models/change.py +47 -0
- cinchdb/models/database.py +20 -0
- cinchdb/models/project.py +20 -0
- cinchdb/models/table.py +86 -0
- cinchdb/models/tenant.py +19 -0
- cinchdb/models/view.py +15 -0
- cinchdb/utils/__init__.py +15 -0
- cinchdb/utils/sql_validator.py +137 -0
- cinchdb-0.1.0.dist-info/METADATA +195 -0
- cinchdb-0.1.0.dist-info/RECORD +68 -0
- cinchdb-0.1.0.dist-info/WHEEL +4 -0
- cinchdb-0.1.0.dist-info/entry_points.txt +3 -0
- cinchdb-0.1.0.dist-info/licenses/LICENSE +201 -0
cinchdb/managers/view.py
ADDED
@@ -0,0 +1,252 @@
|
|
1
|
+
"""View/Model management for CinchDB."""
|
2
|
+
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import List
|
5
|
+
|
6
|
+
from cinchdb.models import View, Change, ChangeType
|
7
|
+
from cinchdb.core.connection import DatabaseConnection
|
8
|
+
from cinchdb.core.path_utils import get_tenant_db_path
|
9
|
+
from cinchdb.core.maintenance import check_maintenance_mode
|
10
|
+
from cinchdb.managers.change_tracker import ChangeTracker
|
11
|
+
|
12
|
+
|
13
|
+
class ViewModel:
|
14
|
+
"""Manages SQL views (models) in the database."""
|
15
|
+
|
16
|
+
def __init__(
|
17
|
+
self, project_root: Path, database: str, branch: str, tenant: str = "main"
|
18
|
+
):
|
19
|
+
"""Initialize view manager.
|
20
|
+
|
21
|
+
Args:
|
22
|
+
project_root: Path to project root
|
23
|
+
database: Database name
|
24
|
+
branch: Branch name
|
25
|
+
tenant: Tenant name (default: main)
|
26
|
+
"""
|
27
|
+
self.project_root = Path(project_root)
|
28
|
+
self.database = database
|
29
|
+
self.branch = branch
|
30
|
+
self.tenant = tenant
|
31
|
+
self.db_path = get_tenant_db_path(project_root, database, branch, tenant)
|
32
|
+
self.change_tracker = ChangeTracker(project_root, database, branch)
|
33
|
+
|
34
|
+
def list_views(self) -> List[View]:
|
35
|
+
"""List all views in the database.
|
36
|
+
|
37
|
+
Returns:
|
38
|
+
List of View objects
|
39
|
+
"""
|
40
|
+
views = []
|
41
|
+
|
42
|
+
with DatabaseConnection(self.db_path) as conn:
|
43
|
+
# Get all views
|
44
|
+
cursor = conn.execute(
|
45
|
+
"""
|
46
|
+
SELECT name, sql FROM sqlite_master
|
47
|
+
WHERE type='view'
|
48
|
+
ORDER BY name
|
49
|
+
"""
|
50
|
+
)
|
51
|
+
|
52
|
+
for row in cursor.fetchall():
|
53
|
+
view = View(
|
54
|
+
name=row["name"],
|
55
|
+
database=self.database,
|
56
|
+
branch=self.branch,
|
57
|
+
sql_statement=row["sql"],
|
58
|
+
)
|
59
|
+
views.append(view)
|
60
|
+
|
61
|
+
return views
|
62
|
+
|
63
|
+
def create_view(self, view_name: str, sql_statement: str) -> View:
|
64
|
+
"""Create a new view.
|
65
|
+
|
66
|
+
Args:
|
67
|
+
view_name: Name of the view
|
68
|
+
sql_statement: SQL SELECT statement defining the view
|
69
|
+
|
70
|
+
Returns:
|
71
|
+
Created View object
|
72
|
+
|
73
|
+
Raises:
|
74
|
+
ValueError: If view already exists
|
75
|
+
MaintenanceError: If branch is in maintenance mode
|
76
|
+
"""
|
77
|
+
# Check maintenance mode
|
78
|
+
check_maintenance_mode(self.project_root, self.database, self.branch)
|
79
|
+
|
80
|
+
# Check if view already exists
|
81
|
+
if self._view_exists(view_name):
|
82
|
+
raise ValueError(f"View '{view_name}' already exists")
|
83
|
+
|
84
|
+
# Create the view
|
85
|
+
create_sql = f"CREATE VIEW {view_name} AS {sql_statement}"
|
86
|
+
|
87
|
+
# Track the change
|
88
|
+
change = Change(
|
89
|
+
type=ChangeType.CREATE_VIEW,
|
90
|
+
entity_type="view",
|
91
|
+
entity_name=view_name,
|
92
|
+
branch=self.branch,
|
93
|
+
details={"sql_statement": sql_statement},
|
94
|
+
sql=create_sql,
|
95
|
+
)
|
96
|
+
self.change_tracker.add_change(change)
|
97
|
+
|
98
|
+
# Apply to all tenants in the branch
|
99
|
+
from cinchdb.managers.change_applier import ChangeApplier
|
100
|
+
|
101
|
+
applier = ChangeApplier(self.project_root, self.database, self.branch)
|
102
|
+
applier.apply_change(change.id)
|
103
|
+
|
104
|
+
# Return the created view
|
105
|
+
return View(
|
106
|
+
name=view_name,
|
107
|
+
database=self.database,
|
108
|
+
branch=self.branch,
|
109
|
+
sql_statement=sql_statement,
|
110
|
+
)
|
111
|
+
|
112
|
+
def update_view(self, view_name: str, sql_statement: str) -> View:
|
113
|
+
"""Update an existing view's SQL.
|
114
|
+
|
115
|
+
Args:
|
116
|
+
view_name: Name of the view to update
|
117
|
+
sql_statement: New SQL SELECT statement
|
118
|
+
|
119
|
+
Returns:
|
120
|
+
Updated View object
|
121
|
+
|
122
|
+
Raises:
|
123
|
+
ValueError: If view doesn't exist
|
124
|
+
MaintenanceError: If branch is in maintenance mode
|
125
|
+
"""
|
126
|
+
# Check maintenance mode
|
127
|
+
check_maintenance_mode(self.project_root, self.database, self.branch)
|
128
|
+
|
129
|
+
# Check if view exists
|
130
|
+
if not self._view_exists(view_name):
|
131
|
+
raise ValueError(f"View '{view_name}' does not exist")
|
132
|
+
|
133
|
+
# SQLite doesn't support CREATE OR REPLACE VIEW, so we need to drop and recreate
|
134
|
+
create_sql = f"CREATE VIEW {view_name} AS {sql_statement}"
|
135
|
+
|
136
|
+
# Track the change
|
137
|
+
change = Change(
|
138
|
+
type=ChangeType.UPDATE_VIEW,
|
139
|
+
entity_type="view",
|
140
|
+
entity_name=view_name,
|
141
|
+
branch=self.branch,
|
142
|
+
details={
|
143
|
+
"sql_statement": sql_statement,
|
144
|
+
"drop_sql": f"DROP VIEW {view_name}",
|
145
|
+
},
|
146
|
+
sql=create_sql,
|
147
|
+
)
|
148
|
+
self.change_tracker.add_change(change)
|
149
|
+
|
150
|
+
# Apply to all tenants in the branch
|
151
|
+
from cinchdb.managers.change_applier import ChangeApplier
|
152
|
+
|
153
|
+
applier = ChangeApplier(self.project_root, self.database, self.branch)
|
154
|
+
applier.apply_change(change.id)
|
155
|
+
|
156
|
+
# Return the updated view
|
157
|
+
return View(
|
158
|
+
name=view_name,
|
159
|
+
database=self.database,
|
160
|
+
branch=self.branch,
|
161
|
+
sql_statement=sql_statement,
|
162
|
+
)
|
163
|
+
|
164
|
+
def delete_view(self, view_name: str) -> None:
|
165
|
+
"""Delete a view.
|
166
|
+
|
167
|
+
Args:
|
168
|
+
view_name: Name of the view to delete
|
169
|
+
|
170
|
+
Raises:
|
171
|
+
ValueError: If view doesn't exist
|
172
|
+
MaintenanceError: If branch is in maintenance mode
|
173
|
+
"""
|
174
|
+
# Check maintenance mode
|
175
|
+
check_maintenance_mode(self.project_root, self.database, self.branch)
|
176
|
+
|
177
|
+
# Check if view exists
|
178
|
+
if not self._view_exists(view_name):
|
179
|
+
raise ValueError(f"View '{view_name}' does not exist")
|
180
|
+
|
181
|
+
# Drop the view
|
182
|
+
drop_sql = f"DROP VIEW {view_name}"
|
183
|
+
|
184
|
+
# Track the change
|
185
|
+
change = Change(
|
186
|
+
type=ChangeType.DROP_VIEW,
|
187
|
+
entity_type="view",
|
188
|
+
entity_name=view_name,
|
189
|
+
branch=self.branch,
|
190
|
+
sql=drop_sql,
|
191
|
+
)
|
192
|
+
self.change_tracker.add_change(change)
|
193
|
+
|
194
|
+
# Apply to all tenants in the branch
|
195
|
+
from cinchdb.managers.change_applier import ChangeApplier
|
196
|
+
|
197
|
+
applier = ChangeApplier(self.project_root, self.database, self.branch)
|
198
|
+
applier.apply_change(change.id)
|
199
|
+
|
200
|
+
def get_view(self, view_name: str) -> View:
|
201
|
+
"""Get information about a specific view.
|
202
|
+
|
203
|
+
Args:
|
204
|
+
view_name: Name of the view
|
205
|
+
|
206
|
+
Returns:
|
207
|
+
View object
|
208
|
+
|
209
|
+
Raises:
|
210
|
+
ValueError: If view doesn't exist
|
211
|
+
"""
|
212
|
+
with DatabaseConnection(self.db_path) as conn:
|
213
|
+
cursor = conn.execute(
|
214
|
+
"SELECT sql FROM sqlite_master WHERE type='view' AND name=?",
|
215
|
+
(view_name,),
|
216
|
+
)
|
217
|
+
row = cursor.fetchone()
|
218
|
+
|
219
|
+
if not row:
|
220
|
+
raise ValueError(f"View '{view_name}' does not exist")
|
221
|
+
|
222
|
+
# Extract the SQL statement (remove CREATE VIEW ... AS prefix)
|
223
|
+
sql = row["sql"]
|
224
|
+
# Find the AS keyword and get everything after it
|
225
|
+
as_index = sql.upper().find(" AS ")
|
226
|
+
if as_index != -1:
|
227
|
+
sql_statement = sql[as_index + 4 :].strip()
|
228
|
+
else:
|
229
|
+
sql_statement = sql
|
230
|
+
|
231
|
+
return View(
|
232
|
+
name=view_name,
|
233
|
+
database=self.database,
|
234
|
+
branch=self.branch,
|
235
|
+
sql_statement=sql_statement,
|
236
|
+
)
|
237
|
+
|
238
|
+
def _view_exists(self, view_name: str) -> bool:
|
239
|
+
"""Check if a view exists.
|
240
|
+
|
241
|
+
Args:
|
242
|
+
view_name: Name of the view
|
243
|
+
|
244
|
+
Returns:
|
245
|
+
True if view exists
|
246
|
+
"""
|
247
|
+
with DatabaseConnection(self.db_path) as conn:
|
248
|
+
cursor = conn.execute(
|
249
|
+
"SELECT name FROM sqlite_master WHERE type='view' AND name=?",
|
250
|
+
(view_name,),
|
251
|
+
)
|
252
|
+
return cursor.fetchone() is not None
|
@@ -0,0 +1,27 @@
|
|
1
|
+
"""Core data models for CinchDB."""
|
2
|
+
|
3
|
+
from .base import CinchDBBaseModel, CinchDBTableModel
|
4
|
+
from .project import Project
|
5
|
+
from .database import Database
|
6
|
+
from .branch import Branch
|
7
|
+
from .tenant import Tenant
|
8
|
+
from .table import Table, Column, ColumnType, ForeignKeyRef, ForeignKeyAction
|
9
|
+
from .view import View
|
10
|
+
from .change import Change, ChangeType
|
11
|
+
|
12
|
+
__all__ = [
|
13
|
+
"CinchDBBaseModel",
|
14
|
+
"CinchDBTableModel",
|
15
|
+
"Project",
|
16
|
+
"Database",
|
17
|
+
"Branch",
|
18
|
+
"Tenant",
|
19
|
+
"Table",
|
20
|
+
"Column",
|
21
|
+
"ColumnType",
|
22
|
+
"ForeignKeyRef",
|
23
|
+
"ForeignKeyAction",
|
24
|
+
"View",
|
25
|
+
"Change",
|
26
|
+
"ChangeType",
|
27
|
+
]
|
cinchdb/models/base.py
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
"""Base models for CinchDB."""
|
2
|
+
|
3
|
+
from datetime import datetime, timezone
|
4
|
+
from typing import Optional, Any
|
5
|
+
import uuid
|
6
|
+
from pydantic import BaseModel, Field, ConfigDict, field_serializer
|
7
|
+
|
8
|
+
|
9
|
+
class CinchDBTableModel(BaseModel):
|
10
|
+
"""Base model for entities that will be stored as database tables.
|
11
|
+
|
12
|
+
Includes automatic id, created_at, and updated_at fields.
|
13
|
+
"""
|
14
|
+
|
15
|
+
model_config = ConfigDict(
|
16
|
+
populate_by_name=True,
|
17
|
+
use_enum_values=True,
|
18
|
+
)
|
19
|
+
|
20
|
+
id: str = Field(
|
21
|
+
default_factory=lambda: str(uuid.uuid4()), description="Unique identifier"
|
22
|
+
)
|
23
|
+
created_at: datetime = Field(
|
24
|
+
default_factory=lambda: datetime.now(timezone.utc),
|
25
|
+
description="Creation timestamp",
|
26
|
+
)
|
27
|
+
updated_at: Optional[datetime] = Field(
|
28
|
+
default=None, description="Last update timestamp"
|
29
|
+
)
|
30
|
+
|
31
|
+
@field_serializer("created_at", "updated_at")
|
32
|
+
def serialize_datetime(self, dt: Optional[datetime], _info: Any) -> Optional[str]:
|
33
|
+
"""Serialize datetime to ISO format."""
|
34
|
+
return dt.isoformat() if dt else None
|
35
|
+
|
36
|
+
|
37
|
+
class CinchDBBaseModel(BaseModel):
|
38
|
+
"""Base model for non-table entities (metadata, config, etc)."""
|
39
|
+
|
40
|
+
model_config = ConfigDict(
|
41
|
+
populate_by_name=True,
|
42
|
+
use_enum_values=True,
|
43
|
+
extra="forbid", # Strict validation for metadata
|
44
|
+
)
|
cinchdb/models/branch.py
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
"""Branch model for CinchDB."""
|
2
|
+
|
3
|
+
from typing import List, Optional, Dict, Any
|
4
|
+
from pydantic import Field
|
5
|
+
from .base import CinchDBBaseModel
|
6
|
+
|
7
|
+
|
8
|
+
class Branch(CinchDBBaseModel):
|
9
|
+
"""Represents a branch within a database."""
|
10
|
+
|
11
|
+
name: str = Field(description="Branch name")
|
12
|
+
database: str = Field(description="Parent database name")
|
13
|
+
parent_branch: Optional[str] = Field(
|
14
|
+
default=None, description="Parent branch this was created from"
|
15
|
+
)
|
16
|
+
tenants: List[str] = Field(
|
17
|
+
default_factory=lambda: ["main"], description="List of tenant names"
|
18
|
+
)
|
19
|
+
metadata: Dict[str, Any] = Field(
|
20
|
+
default_factory=dict, description="Branch metadata"
|
21
|
+
)
|
22
|
+
is_main: bool = Field(default=False, description="Whether this is the main branch")
|
23
|
+
|
24
|
+
def can_delete(self) -> bool:
|
25
|
+
"""Check if this branch can be deleted."""
|
26
|
+
return self.name != "main" and not self.is_main
|
cinchdb/models/change.py
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
"""Change tracking models for CinchDB."""
|
2
|
+
|
3
|
+
from enum import Enum
|
4
|
+
from typing import Dict, Any, Optional
|
5
|
+
from pydantic import Field
|
6
|
+
from .base import CinchDBTableModel
|
7
|
+
|
8
|
+
|
9
|
+
class ChangeType(str, Enum):
|
10
|
+
"""Types of schema changes."""
|
11
|
+
|
12
|
+
# Table changes
|
13
|
+
CREATE_TABLE = "create_table"
|
14
|
+
DROP_TABLE = "drop_table"
|
15
|
+
RENAME_TABLE = "rename_table"
|
16
|
+
|
17
|
+
# Column changes
|
18
|
+
ADD_COLUMN = "add_column"
|
19
|
+
DROP_COLUMN = "drop_column"
|
20
|
+
RENAME_COLUMN = "rename_column"
|
21
|
+
MODIFY_COLUMN = "modify_column"
|
22
|
+
ALTER_COLUMN_NULLABLE = "alter_column_nullable"
|
23
|
+
|
24
|
+
# View changes
|
25
|
+
CREATE_VIEW = "create_view"
|
26
|
+
DROP_VIEW = "drop_view"
|
27
|
+
UPDATE_VIEW = "update_view"
|
28
|
+
|
29
|
+
# Index changes
|
30
|
+
CREATE_INDEX = "create_index"
|
31
|
+
DROP_INDEX = "drop_index"
|
32
|
+
|
33
|
+
|
34
|
+
class Change(CinchDBTableModel):
|
35
|
+
"""Represents a schema change in the database."""
|
36
|
+
|
37
|
+
type: ChangeType = Field(description="Type of change")
|
38
|
+
entity_type: str = Field(description="Type of entity affected (table, view, index)")
|
39
|
+
entity_name: str = Field(description="Name of the affected entity")
|
40
|
+
details: Dict[str, Any] = Field(
|
41
|
+
default_factory=dict, description="Change-specific details"
|
42
|
+
)
|
43
|
+
sql: Optional[str] = Field(
|
44
|
+
default=None, description="SQL statement that implements the change"
|
45
|
+
)
|
46
|
+
branch: str = Field(description="Branch where change was made")
|
47
|
+
applied: bool = Field(default=False, description="Whether change has been applied")
|
@@ -0,0 +1,20 @@
|
|
1
|
+
"""Database model for CinchDB."""
|
2
|
+
|
3
|
+
from typing import List, Optional
|
4
|
+
from pydantic import Field
|
5
|
+
from .base import CinchDBBaseModel
|
6
|
+
|
7
|
+
|
8
|
+
class Database(CinchDBBaseModel):
|
9
|
+
"""Represents a database within a CinchDB project."""
|
10
|
+
|
11
|
+
name: str = Field(description="Database name")
|
12
|
+
branches: List[str] = Field(
|
13
|
+
default_factory=lambda: ["main"], description="List of branch names"
|
14
|
+
)
|
15
|
+
active_branch: str = Field(default="main", description="Currently active branch")
|
16
|
+
description: Optional[str] = Field(default=None, description="Database description")
|
17
|
+
|
18
|
+
def can_delete(self) -> bool:
|
19
|
+
"""Check if this database can be deleted."""
|
20
|
+
return self.name != "main"
|
@@ -0,0 +1,20 @@
|
|
1
|
+
"""Project model for CinchDB."""
|
2
|
+
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import List, Optional
|
5
|
+
from pydantic import Field
|
6
|
+
from .base import CinchDBBaseModel
|
7
|
+
|
8
|
+
|
9
|
+
class Project(CinchDBBaseModel):
|
10
|
+
"""Represents a CinchDB project."""
|
11
|
+
|
12
|
+
name: str = Field(description="Project name")
|
13
|
+
path: Path = Field(description="Path to project directory")
|
14
|
+
databases: List[str] = Field(
|
15
|
+
default_factory=list, description="List of database names"
|
16
|
+
)
|
17
|
+
active_database: str = Field(
|
18
|
+
default="main", description="Currently active database"
|
19
|
+
)
|
20
|
+
description: Optional[str] = Field(default=None, description="Project description")
|
cinchdb/models/table.py
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
"""Table and Column models for CinchDB."""
|
2
|
+
|
3
|
+
from typing import List, Optional, Literal
|
4
|
+
from pydantic import BaseModel, Field, ConfigDict
|
5
|
+
from .base import CinchDBBaseModel
|
6
|
+
|
7
|
+
|
8
|
+
# SQLite column types
|
9
|
+
ColumnType = Literal["TEXT", "INTEGER", "REAL", "BLOB", "NUMERIC"]
|
10
|
+
|
11
|
+
# Foreign key actions
|
12
|
+
ForeignKeyAction = Literal["CASCADE", "SET NULL", "RESTRICT", "NO ACTION"]
|
13
|
+
|
14
|
+
|
15
|
+
class ForeignKeyRef(BaseModel):
|
16
|
+
"""Foreign key reference specification."""
|
17
|
+
|
18
|
+
model_config = ConfigDict(extra="forbid")
|
19
|
+
|
20
|
+
table: str = Field(description="Referenced table name")
|
21
|
+
column: str = Field(default="id", description="Referenced column name")
|
22
|
+
on_delete: ForeignKeyAction = Field(
|
23
|
+
default="RESTRICT",
|
24
|
+
description="Action on delete of referenced row"
|
25
|
+
)
|
26
|
+
on_update: ForeignKeyAction = Field(
|
27
|
+
default="RESTRICT",
|
28
|
+
description="Action on update of referenced row"
|
29
|
+
)
|
30
|
+
|
31
|
+
|
32
|
+
class Column(BaseModel):
|
33
|
+
"""Represents a column in a table."""
|
34
|
+
|
35
|
+
model_config = ConfigDict(extra="forbid")
|
36
|
+
|
37
|
+
name: str = Field(description="Column name")
|
38
|
+
type: ColumnType = Field(description="SQLite column type")
|
39
|
+
nullable: bool = Field(
|
40
|
+
default=True, description="Whether column allows NULL values"
|
41
|
+
)
|
42
|
+
default: Optional[str] = Field(
|
43
|
+
default=None, description="Default value SQL expression"
|
44
|
+
)
|
45
|
+
primary_key: bool = Field(
|
46
|
+
default=False, description="Whether this is a primary key"
|
47
|
+
)
|
48
|
+
unique: bool = Field(default=False, description="Whether values must be unique")
|
49
|
+
foreign_key: Optional[ForeignKeyRef] = Field(
|
50
|
+
default=None,
|
51
|
+
description="Foreign key constraint specification"
|
52
|
+
)
|
53
|
+
|
54
|
+
|
55
|
+
class Table(CinchDBBaseModel):
|
56
|
+
"""Represents a table in the database."""
|
57
|
+
|
58
|
+
name: str = Field(description="Table name")
|
59
|
+
database: str = Field(description="Database name")
|
60
|
+
branch: str = Field(description="Branch name")
|
61
|
+
columns: List[Column] = Field(default_factory=list, description="Table columns")
|
62
|
+
|
63
|
+
def __init__(self, **data):
|
64
|
+
"""Initialize table with default columns."""
|
65
|
+
super().__init__(**data)
|
66
|
+
|
67
|
+
# Add default columns if not present
|
68
|
+
column_names = {col.name for col in self.columns}
|
69
|
+
|
70
|
+
if "id" not in column_names:
|
71
|
+
self.columns.insert(
|
72
|
+
0,
|
73
|
+
Column(
|
74
|
+
name="id",
|
75
|
+
type="TEXT",
|
76
|
+
nullable=False,
|
77
|
+
primary_key=True,
|
78
|
+
unique=True,
|
79
|
+
),
|
80
|
+
)
|
81
|
+
|
82
|
+
if "created_at" not in column_names:
|
83
|
+
self.columns.append(Column(name="created_at", type="TEXT", nullable=False))
|
84
|
+
|
85
|
+
if "updated_at" not in column_names:
|
86
|
+
self.columns.append(Column(name="updated_at", type="TEXT", nullable=True))
|
cinchdb/models/tenant.py
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
"""Tenant model for CinchDB."""
|
2
|
+
|
3
|
+
from typing import Optional
|
4
|
+
from pydantic import Field
|
5
|
+
from .base import CinchDBBaseModel
|
6
|
+
|
7
|
+
|
8
|
+
class Tenant(CinchDBBaseModel):
|
9
|
+
"""Represents a tenant within a branch."""
|
10
|
+
|
11
|
+
name: str = Field(description="Tenant name")
|
12
|
+
branch: str = Field(description="Parent branch name")
|
13
|
+
database: str = Field(description="Parent database name")
|
14
|
+
description: Optional[str] = Field(default=None, description="Tenant description")
|
15
|
+
is_main: bool = Field(default=False, description="Whether this is the main tenant")
|
16
|
+
|
17
|
+
def can_delete(self) -> bool:
|
18
|
+
"""Check if this tenant can be deleted."""
|
19
|
+
return self.name != "main" and not self.is_main
|
cinchdb/models/view.py
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
"""View model for CinchDB."""
|
2
|
+
|
3
|
+
from typing import Optional
|
4
|
+
from pydantic import Field
|
5
|
+
from .base import CinchDBBaseModel
|
6
|
+
|
7
|
+
|
8
|
+
class View(CinchDBBaseModel):
|
9
|
+
"""Represents a SQL view in the database."""
|
10
|
+
|
11
|
+
name: str = Field(description="View name")
|
12
|
+
database: str = Field(description="Database name")
|
13
|
+
branch: str = Field(description="Branch name")
|
14
|
+
sql_statement: str = Field(description="SQL SELECT statement that defines the view")
|
15
|
+
description: Optional[str] = Field(default=None, description="View description")
|
@@ -0,0 +1,15 @@
|
|
1
|
+
"""Utility modules for CinchDB."""
|
2
|
+
|
3
|
+
from cinchdb.utils.sql_validator import (
|
4
|
+
validate_sql_query,
|
5
|
+
validate_query_safe,
|
6
|
+
SQLValidationError,
|
7
|
+
SQLOperation
|
8
|
+
)
|
9
|
+
|
10
|
+
__all__ = [
|
11
|
+
"validate_sql_query",
|
12
|
+
"validate_query_safe",
|
13
|
+
"SQLValidationError",
|
14
|
+
"SQLOperation"
|
15
|
+
]
|