Skip to content

Commit 0aa31cf

Browse files
committed
up
1 parent a54cecc commit 0aa31cf

8 files changed

Lines changed: 1083 additions & 981 deletions

File tree

abstra_json_sql/cli.py

Lines changed: 62 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def interactive_create_table(workdir: Path):
3232
"""Create a table interactively by asking user for table name, columns, etc."""
3333
try:
3434
tables = FileSystemJsonLTables(workdir=workdir)
35-
35+
3636
# Get table name
3737
print("Creating a new table...")
3838
while True:
@@ -44,49 +44,65 @@ def interactive_create_table(workdir: Path):
4444
# Check if table already exists
4545
existing_table = tables.get_table(table_name)
4646
if existing_table:
47-
print(f"Table '{table_name}' already exists. Please choose a different name.")
47+
print(
48+
f"Table '{table_name}' already exists. Please choose a different name."
49+
)
4850
continue
4951
break
5052
except FileNotFoundError:
5153
# Table doesn't exist, which is what we want
5254
break
53-
55+
5456
# Get columns
5557
columns = []
5658
print(f"\nNow let's add columns to the '{table_name}' table.")
5759
print("Available column types: int, string, float, bool")
5860
print("Press Enter with empty column name to finish adding columns.\n")
59-
61+
6062
while True:
6163
column_name = input("Column name: ").strip()
6264
if not column_name:
6365
if len(columns) == 0:
6466
print("At least one column is required. Please add a column.")
6567
continue
6668
break
67-
69+
6870
# Check if column name already exists
6971
if any(col.name == column_name for col in columns):
70-
print(f"Column '{column_name}' already exists. Please choose a different name.")
72+
print(
73+
f"Column '{column_name}' already exists. Please choose a different name."
74+
)
7175
continue
72-
76+
7377
# Get column type
7478
while True:
75-
column_type_str = input(f"Column type for '{column_name}' (int/string/float/bool): ").strip().lower()
79+
column_type_str = (
80+
input(f"Column type for '{column_name}' (int/string/float/bool): ")
81+
.strip()
82+
.lower()
83+
)
7684
if column_type_str in ["int", "string", "float", "bool"]:
7785
column_type = ColumnType(column_type_str)
7886
break
7987
else:
80-
print("Invalid column type. Please enter: int, string, float, or bool")
81-
88+
print(
89+
"Invalid column type. Please enter: int, string, float, or bool"
90+
)
91+
8292
# Ask if it's a primary key
83-
is_primary = input(f"Is '{column_name}' a primary key? (y/N): ").strip().lower()
84-
is_primary_key = is_primary in ['y', 'yes']
85-
93+
is_primary = (
94+
input(f"Is '{column_name}' a primary key? (y/N): ").strip().lower()
95+
)
96+
is_primary_key = is_primary in ["y", "yes"]
97+
8698
# Ask for default value
8799
default_value = None
88-
has_default = input(f"Does '{column_name}' have a default value? (y/N): ").strip().lower()
89-
if has_default in ['y', 'yes']:
100+
has_default = (
101+
input(f"Does '{column_name}' have a default value? (y/N): ")
102+
.strip()
103+
.lower()
104+
)
105+
if has_default in ["y", "yes"]:
90106
while True:
91107
default_str = input(f"Default value for '{column_name}': ").strip()
92108
try:
@@ -96,41 +112,45 @@ def interactive_create_table(workdir: Path):
96112
elif column_type == ColumnType.float:
97113
default_value = float(default_str) if default_str else None
98114
elif column_type == ColumnType.bool:
99-
if default_str.lower() in ['true', '1', 'yes', 'y']:
115+
if default_str.lower() in ["true", "1", "yes", "y"]:
100116
default_value = True
101-
elif default_str.lower() in ['false', '0', 'no', 'n']:
117+
elif default_str.lower() in ["false", "0", "no", "n"]:
102118
default_value = False
103-
elif default_str == '':
119+
elif default_str == "":
104120
default_value = None
105121
else:
106122
raise ValueError("Invalid boolean value")
107123
else: # string
108124
default_value = default_str if default_str else None
109125
break
110126
except ValueError:
111-
print(f"Invalid default value for {column_type.value} type. Please try again.")
112-
127+
print(
128+
f"Invalid default value for {column_type.value} type. Please try again."
129+
)
130+
113131
# Create column
114132
column = Column(
115133
name=column_name,
116134
type=column_type,
117135
is_primary_key=is_primary_key,
118-
default=default_value
136+
default=default_value,
119137
)
120138
columns.append(column)
121139
print(f"✓ Added column '{column_name}' ({column_type.value})")
122-
140+
123141
# Create and add the table
124142
table = Table(name=table_name, columns=columns, data=[])
125143
tables.add_table(table)
126-
144+
127145
print(f"\n✓ Table '{table_name}' created successfully!")
128146
print("Columns:")
129147
for col in columns:
130148
pk_indicator = " (PRIMARY KEY)" if col.is_primary_key else ""
131-
default_indicator = f" (default: {col.default})" if col.default is not None else ""
149+
default_indicator = (
150+
f" (default: {col.default})" if col.default is not None else ""
151+
)
132152
print(f" - {col.name}: {col.type.value}{pk_indicator}{default_indicator}")
133-
153+
134154
except KeyboardInterrupt:
135155
print("\n\nTable creation cancelled.")
136156
except Exception as e:
@@ -139,7 +159,7 @@ def interactive_create_table(workdir: Path):
139159

140160
def main():
141161
parser = ArgumentParser(description="Run SQL queries on JSON files.")
142-
162+
143163
# Add top-level arguments for backward compatibility
144164
parser.add_argument("--code", type=str, help="SQL query to execute", default=None)
145165
parser.add_argument(
@@ -161,13 +181,15 @@ def main():
161181
choices=["json", "csv"],
162182
help="Output format (default: json)",
163183
)
164-
184+
165185
# Add subcommands
166186
subparsers = parser.add_subparsers(dest="command", help="Available commands")
167-
187+
168188
# Query command
169189
query_parser = subparsers.add_parser("query", help="Run SQL queries")
170-
query_parser.add_argument("--code", type=str, help="SQL query to execute", default=None)
190+
query_parser.add_argument(
191+
"--code", type=str, help="SQL query to execute", default=None
192+
)
171193
query_parser.add_argument(
172194
"--workdir",
173195
type=Path,
@@ -187,26 +209,26 @@ def main():
187209
choices=["json", "csv"],
188210
help="Output format (default: json)",
189211
)
190-
212+
191213
# Create table command
192214
create_parser = subparsers.add_parser("create", help="Create database objects")
193-
create_subparsers = create_parser.add_subparsers(dest="create_command", help="Create commands")
194-
215+
create_subparsers = create_parser.add_subparsers(
216+
dest="create_command", help="Create commands"
217+
)
218+
195219
table_parser = create_subparsers.add_parser("table", help="Create a table")
196220
table_parser.add_argument(
197-
"--interactive",
198-
action="store_true",
199-
help="Create table interactively"
221+
"--interactive", action="store_true", help="Create table interactively"
200222
)
201223
table_parser.add_argument(
202224
"--workdir",
203225
type=Path,
204226
default=Path.cwd(),
205227
help="Directory to create the table in",
206228
)
207-
229+
208230
args = parser.parse_args()
209-
231+
210232
# Handle commands
211233
if args.command == "create" and args.create_command == "table":
212234
if args.interactive:
@@ -246,7 +268,9 @@ def main():
246268
else:
247269
print("JSON SQL CLI")
248270
print("Type 'exit' to quit.")
249-
print("Tip: Use 'abstra-json-sql create table --interactive' to create tables.")
271+
print(
272+
"Tip: Use 'abstra-json-sql create table --interactive' to create tables."
273+
)
250274
while True:
251275
try:
252276
code = input("> ")
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Persistence Implementations
2+
3+
This directory contains the different persistence implementations for the table storage system.
4+
5+
## Structure
6+
7+
- `memory.py` - In-memory table storage (`InMemoryTables`)
8+
- `json.py` - File system storage using JSON files (`FileSystemJsonTables`)
9+
- `jsonl.py` - File system storage using JSONL files (`FileSystemJsonLTables`)
10+
- `extended.py` - Extended tables that combine a base persistence layer with additional in-memory tables (`ExtendedTables`)
11+
12+
## Usage
13+
14+
All persistence implementations conform to the `ITablesSnapshot` interface defined in the main `tables.py` file. You can import them directly from the main module:
15+
16+
```python
17+
from abstra_json_sql.tables import (
18+
InMemoryTables,
19+
FileSystemJsonTables,
20+
FileSystemJsonLTables,
21+
ExtendedTables
22+
)
23+
```
24+
25+
Or import them from the persistence module:
26+
27+
```python
28+
from abstra_json_sql.persistence import (
29+
InMemoryTables,
30+
FileSystemJsonTables,
31+
FileSystemJsonLTables,
32+
ExtendedTables
33+
)
34+
```
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"""Persistence implementations for table storage."""
2+
3+
from .memory import InMemoryTables
4+
from .json import FileSystemJsonTables
5+
from .jsonl import FileSystemJsonLTables
6+
from .extended import ExtendedTables
7+
8+
__all__ = [
9+
"InMemoryTables",
10+
"FileSystemJsonTables",
11+
"FileSystemJsonLTables",
12+
"ExtendedTables",
13+
]
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
from typing import List, Optional
2+
3+
from ..tables import ITablesSnapshot, Table, Column, ColumnType
4+
5+
6+
class ExtendedTables(ITablesSnapshot):
7+
snapshot: ITablesSnapshot
8+
extra_tables: List[Table]
9+
10+
def __init__(self, snapshot: ITablesSnapshot, tables: List[Table]):
11+
self.snapshot = snapshot
12+
self.extra_tables = []
13+
14+
# Convert existing table data to column ID format if needed
15+
for table in tables:
16+
converted_data = []
17+
for row in table.data:
18+
converted_row = table.convert_row_to_column_ids(row)
19+
converted_data.append(converted_row)
20+
table.data = converted_data
21+
self.extra_tables.append(table)
22+
23+
def get_table(self, name: str) -> Optional[Table]:
24+
table = self.snapshot.get_table(name)
25+
if table:
26+
return table
27+
for table in self.extra_tables:
28+
if table.name == name:
29+
# Create a copy with data converted to column names
30+
converted_data = []
31+
for row in table.data:
32+
converted_data.append(table.convert_row_from_column_ids(row))
33+
34+
result_table = Table(
35+
name=table.name,
36+
columns=table.columns,
37+
data=converted_data,
38+
table_id=table.table_id,
39+
)
40+
return result_table
41+
return None
42+
43+
def add_table(self, table: Table):
44+
self.extra_tables.append(table)
45+
46+
def remove_table(self, name: str):
47+
self.extra_tables = [table for table in self.extra_tables if table.name != name]
48+
49+
def rename_table(self, old_name: str, new_name: str):
50+
for table in self.extra_tables:
51+
if table.name == old_name:
52+
table.name = new_name
53+
return
54+
self.snapshot.rename_table(old_name, new_name)
55+
56+
def add_column(self, table_name: str, column: Column):
57+
for table in self.extra_tables:
58+
if table.name == table_name:
59+
table.columns.append(column)
60+
# Add default value to existing rows using column ID
61+
for row in table.data:
62+
row[column.column_id] = column.default
63+
return
64+
self.snapshot.add_column(table_name, column)
65+
66+
def remove_column(self, table_name: str, column_name: str):
67+
for table in self.extra_tables:
68+
if table.name == table_name:
69+
# Find the column to get its ID before removing
70+
column_to_remove = table.get_column(column_name)
71+
table.columns = [
72+
col for col in table.columns if col.name != column_name
73+
]
74+
# Remove column from existing rows using column ID
75+
if column_to_remove:
76+
for row in table.data:
77+
row.pop(column_to_remove.column_id, None)
78+
return
79+
self.snapshot.remove_column(table_name, column_name)
80+
81+
def rename_column(self, table_name: str, old_name: str, new_name: str):
82+
for table in self.extra_tables:
83+
if table.name == table_name:
84+
# Update column name (data doesn't need to change since we use column IDs)
85+
for col in table.columns:
86+
if col.name == old_name:
87+
col.name = new_name
88+
break
89+
return
90+
self.snapshot.rename_column(table_name, old_name, new_name)
91+
92+
def change_column_type(
93+
self, table_name: str, column_name: str, new_type: ColumnType
94+
):
95+
for table in self.extra_tables:
96+
if table.name == table_name:
97+
for col in table.columns:
98+
if col.name == column_name:
99+
col.type = new_type
100+
return
101+
self.snapshot.change_column_type(table_name, column_name, new_type)
102+
103+
def insert(self, table_name: str, row: dict):
104+
for table in self.extra_tables:
105+
if table.name == table_name:
106+
# Convert row from column names to column IDs
107+
row_with_ids = table.convert_row_to_column_ids(row)
108+
table.data.append(row_with_ids)
109+
return
110+
self.snapshot.insert(table_name, row)
111+
112+
def update(self, table_name, idx, changes):
113+
for table in self.extra_tables:
114+
if table.name == table_name:
115+
# Convert changes from column names to column IDs
116+
changes_with_ids = table.convert_row_to_column_ids(changes)
117+
table.data[idx].update(changes_with_ids)
118+
return
119+
self.snapshot.update(table_name, idx, changes)
120+
121+
def delete(self, table_name: str, idxs: List[int]):
122+
for table in self.extra_tables:
123+
if table.name == table_name:
124+
# Sort indices in descending order to avoid index shifting
125+
for idx in sorted(idxs, reverse=True):
126+
if 0 <= idx < len(table.data):
127+
del table.data[idx]
128+
return
129+
self.snapshot.delete(table_name, idxs)

0 commit comments

Comments
 (0)