Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement tables.add rpc endpoint #3614

Merged
merged 6 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion db/tables/operations/create.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from sqlalchemy.ext import compiler
from sqlalchemy.schema import DDLElement
import json
from db.connection import execute_msar_func_with_engine
from db.connection import execute_msar_func_with_engine, exec_msar_func
from db.types.base import PostgresType
from db.tables.operations.select import reflect_table_from_oid
from db.metadata import get_empty_metadata
Expand Down Expand Up @@ -33,6 +33,38 @@ def create_mathesar_table(engine, table_name, schema_oid, columns=[], constraint
).fetchone()[0]


def create_table_on_database(
table_name,
schema_oid,
conn,
column_data_list=[],
constraint_data_list=[],
comment=None
):
"""
Creates a table with a default id column.

Args:
table_name: Name of the table to be created.
schema_oid: The OID of the schema where the table will be created.
columns: The columns dict for the new table, in order. (optional)
constraints: The constraints dict for the new table. (optional)
comment: The comment for the new table. (optional)

Returns:
Returns the OID of the created table.
"""
return exec_msar_func(
conn,
'add_mathesar_table',
schema_oid,
table_name,
json.dumps(column_data_list),
json.dumps(constraint_data_list),
comment
).fetchone()[0]


# TODO stop relying on reflections, instead return oid of the created table.
def create_string_column_table(name, schema_oid, column_names, engine, comment=None):
"""
Expand Down
3 changes: 2 additions & 1 deletion docs/docs/api/rpc.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ To use an RPC function:
options:
members:
- list_
- get_
- get
- add
- delete
- TableInfo

Expand Down
22 changes: 22 additions & 0 deletions mathesar/rpc/columns.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,28 @@ def from_dict(cls, col_default):
)


class CreateableColumnInfo(TypedDict):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At some point, we need to figure out a way to use inheritance here in a way that preserves the documentary value of these classes.

"""
Information about adding a new column.

Only the `name` & `type` keys are required.

Attributes:
name: The name of the column.
type: The type of the column on the database.
type_options: The options applied to the column type.
nullable: Whether or not the column is nullable.
default: The default value.
description: The description of the column.
"""
name: str
type: str
type_options: Optional[TypeOptions]
nullable: Optional[bool]
default: Optional[ColumnDefault]
description: Optional[str]


class SettableColumnInfo(TypedDict):
"""
Information about a column, restricted to settable fields.
Expand Down
8 changes: 8 additions & 0 deletions mathesar/rpc/constraints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""
Classes and functions exposed to the RPC endpoint for managing table constraints.
"""
from typing import TypedDict


class CreateableConstraintInfo(TypedDict):
pass
38 changes: 38 additions & 0 deletions mathesar/rpc/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@

from db.tables.operations.select import get_table_info, get_table
from db.tables.operations.drop import drop_table_from_database
from db.tables.operations.create import create_table_on_database
from mathesar.rpc.columns import CreateableColumnInfo
from mathesar.rpc.constraints import CreateableConstraintInfo
from mathesar.rpc.exceptions.handlers import handle_rpc_exceptions
from mathesar.rpc.utils import connect

Expand Down Expand Up @@ -67,6 +70,41 @@ def get(*, table_oid: int, database_id: int, **kwargs) -> TableInfo:
return TableInfo(raw_table_info)


@rpc_method(name="tables.add")
@http_basic_auth_login_required
@handle_rpc_exceptions
def add(
*,
table_name: str,
schema_oid: int,
database_id: int,
column_data_list: list[CreateableColumnInfo] = [],
constraint_data_list: list[CreateableConstraintInfo] = [],
comment: str = None,
**kwargs
) -> int:
"""
Add a table with a default id column.

Args:
table_name: Name of the table to be created.
schema_oid: Identity of the schema in the user's database.
database_id: The Django id of the database containing the table.
column_data_list: A list describing columns to be created for the new table, in order.
constraint_data_list: A list describing constraints to be created for the new table.
comment: The comment for the new table.

Returns:
The `oid` of the created table.
"""
user = kwargs.get(REQUEST_KEY).user
with connect(database_id, user) as conn:
created_table_oid = create_table_on_database(
table_name, schema_oid, conn, column_data_list, constraint_data_list, comment
)
return created_table_oid
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may need to enhance this return value. The current endpoint returns a blob describing the created table whenever one is created. OTOH, the only info in the blob we have set up for tables.list which wouldn't have been directly submitted is the OID, which this does return.

Let's merge as-is, and keep a mental note to watch for feature requests from the front end on this.



@rpc_method(name="tables.delete")
@http_basic_auth_login_required
@handle_rpc_exceptions
Expand Down
5 changes: 5 additions & 0 deletions mathesar/tests/rpc/test_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@
"tables.get",
[user_is_authenticated]
),
(
tables.add,
"tables.add",
[user_is_authenticated]
),
(
tables.delete,
"tables.delete",
Expand Down
26 changes: 26 additions & 0 deletions mathesar/tests/rpc/test_tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,29 @@ def mock_drop_table(_table_oid, conn, cascade):
monkeypatch.setattr(tables, 'drop_table_from_database', mock_drop_table)
deleted_table = tables.delete(table_oid=1964474, database_id=11, request=request)
assert deleted_table == 'public."Table 0"'


def test_tables_add(rf, monkeypatch):
request = rf.post('/api/rpc/v0', data={})
request.user = User(username='alice', password='pass1234')
schema_oid = 2200
database_id = 11

@contextmanager
def mock_connect(_database_id, user):
if _database_id == database_id and user.username == 'alice':
try:
yield True
finally:
pass
else:
raise AssertionError('incorrect parameters passed')

def mock_table_add(table_name, _schema_oid, conn, column_data_list, constraint_data_list, comment):
if _schema_oid != schema_oid:
raise AssertionError('incorrect parameters passed')
return 1964474
monkeypatch.setattr(tables, 'connect', mock_connect)
monkeypatch.setattr(tables, 'create_table_on_database', mock_table_add)
actual_table_oid = tables.add(table_name='newtable', schema_oid=2200, database_id=11, request=request)
assert actual_table_oid == 1964474
Loading