Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…esar into transfer-ownership-1
  • Loading branch information
pavish committed Sep 19, 2024
2 parents d2707f1 + f8b81c2 commit e487813
Show file tree
Hide file tree
Showing 17 changed files with 164 additions and 136 deletions.
5 changes: 4 additions & 1 deletion db/schemas/operations/alter.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,8 @@ def patch_schema(schema_oid, conn, patch):
patch: A dict mapping the following fields to new values:
- 'name' (optional): New name for the schema.
- 'description' (optional): New description for the schema.
Returns:
The SchemaInfo describing the user-defined schema in the database.
"""
exec_msar_func(conn, "patch_schema", schema_oid, json.dumps(patch))
return exec_msar_func(conn, "patch_schema", schema_oid, json.dumps(patch)).fetchone()[0]
2 changes: 1 addition & 1 deletion db/schemas/operations/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,6 @@ def create_schema(schema_name, conn, description=None):
If a schema already exists with the given name, this function will raise an error.
Returns:
The integer oid of the newly created schema.
The SchemaInfo describing the user-defined schema in the database.
"""
return exec_msar_func(conn, 'create_schema', schema_name, description).fetchone()[0]
4 changes: 4 additions & 0 deletions db/schemas/operations/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ def list_schemas(conn):
return exec_msar_func(conn, 'list_schemas').fetchone()[0]


def get_schema(schema_oid, conn):
return exec_msar_func(conn, 'get_schema').fetchone()[0]


def reflect_schema(engine, name=None, oid=None, metadata=None):
# If we have both arguments, the behavior is undefined.
try:
Expand Down
34 changes: 25 additions & 9 deletions db/sql/00_msar.sql
Original file line number Diff line number Diff line change
Expand Up @@ -1585,7 +1585,8 @@ END;
$$ LANGUAGE plpgsql;


CREATE OR REPLACE FUNCTION msar.patch_schema(sch_id oid, patch jsonb) RETURNS void AS $$/*
DROP FUNCTION IF EXISTS msar.patch_schema(oid, jsonb);
CREATE OR REPLACE FUNCTION msar.patch_schema(sch_id oid, patch jsonb) RETURNS jsonb AS $$/*
Modify a schema according to the given patch.
Args:
Expand All @@ -1594,11 +1595,15 @@ Args:
- name: (optional) The new name of the schema
- description: (optional) The new description of the schema. To remove a description, pass an
empty string or NULL.
Returns:
A json object describing the user-defined schema in the database.
*/
BEGIN
PERFORM msar.rename_schema(sch_id, patch->>'name');
PERFORM CASE WHEN patch ? 'description'
THEN msar.set_schema_description(sch_id, patch->>'description') END;
RETURN msar.get_schema(sch_id);
END;
$$ LANGUAGE plpgsql RETURNS NULL ON NULL INPUT;

Expand Down Expand Up @@ -1645,10 +1650,11 @@ END;
$$ LANGUAGE plpgsql;


DROP FUNCTION IF EXISTS msar.create_schema(text, text);
CREATE OR REPLACE FUNCTION msar.create_schema(
sch_name text,
description text DEFAULT ''
) RETURNS oid AS $$/*
) RETURNS jsonb AS $$/*
Create a schema, possibly with a description.
If a schema with the given name already exists, an exception will be raised.
Expand All @@ -1658,7 +1664,7 @@ Args:
description: (optional) A description for the schema, UNQUOTED.
Returns:
The integer OID of the schema
A json object describing the user-defined schema in the database.
Note: This function does not support IF NOT EXISTS because it's simpler that way. I originally tried
to support descriptions and if_not_exists in the same function, but as I discovered more edge cases
Expand All @@ -1669,7 +1675,7 @@ BEGIN
EXECUTE 'CREATE SCHEMA ' || quote_ident(sch_name);
schema_oid := msar.get_schema_oid(sch_name);
PERFORM msar.set_schema_description(schema_oid, description);
RETURN schema_oid;
RETURN msar.get_schema(schema_oid);
END;
$$ LANGUAGE plpgsql;

Expand Down Expand Up @@ -4591,9 +4597,11 @@ WITH fkey_map_cte AS (SELECT * FROM msar.get_fkey_map_table(tab_id))
SELECT 'jsonb_build_object(' || string_agg(
format(
$j$
%1$L, jsonb_object_agg(
summary_cte_%1$s.fkey, summary_cte_%1$s.summary
) FILTER (WHERE summary_cte_%1$s.fkey IS NOT NULL)
%1$L, COALESCE(
jsonb_object_agg(
summary_cte_%1$s.fkey, summary_cte_%1$s.summary
) FILTER (WHERE summary_cte_%1$s.fkey IS NOT NULL), '{}'::jsonb
)
$j$,
conkey
), ', '
Expand All @@ -4606,7 +4614,13 @@ CREATE OR REPLACE FUNCTION
msar.build_self_summary_json_expr(tab_id oid) RETURNS TEXT AS $$/*
*/
SELECT CASE WHEN quote_ident(msar.get_selectable_pkey_attnum(tab_id)::text) IS NOT NULL THEN
'jsonb_object_agg(summary_cte_self.key, summary_cte_self.summary) FILTER (WHERE summary_cte_self.key IS NOT NULL)'
$j$
COALESCE(
jsonb_object_agg(
summary_cte_self.key, summary_cte_self.summary
) FILTER (WHERE summary_cte_self.key IS NOT NULL), '{}'::jsonb
)
$j$
END;
$$ LANGUAGE SQL STABLE RETURNS NULL ON NULL INPUT;

Expand Down Expand Up @@ -4775,10 +4789,12 @@ END;
$$ LANGUAGE plpgsql;


DROP FUNCTION IF EXISTS msar.get_record_from_table(oid, anyelement);
DROP FUNCTION IF EXISTS msar.get_record_from_table(oid, anyelement, boolean);
CREATE OR REPLACE FUNCTION
msar.get_record_from_table(
tab_id oid,
rec_id anyelement,
rec_id anycompatible,
return_record_summaries boolean DEFAULT false
) RETURNS jsonb AS $$/*
Get single record from a table. Only columns to which the user has access are returned.
Expand Down
60 changes: 21 additions & 39 deletions db/sql/test_00_msar.sql
Original file line number Diff line number Diff line change
Expand Up @@ -1188,7 +1188,7 @@ $$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION test_create_schema_without_description() RETURNS SETOF TEXT AS $$
DECLARE sch_oid oid;
BEGIN
SELECT msar.create_schema('foo bar') INTO sch_oid;
SELECT msar.create_schema('foo bar') ->> 'oid' INTO sch_oid;
RETURN NEXT has_schema('foo bar');
RETURN NEXT is(sch_oid, msar.get_schema_oid('foo bar'));
RETURN NEXT is(obj_description(sch_oid), NULL);
Expand All @@ -1199,7 +1199,7 @@ $$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION test_create_schema_with_description() RETURNS SETOF TEXT AS $$
DECLARE sch_oid oid;
BEGIN
SELECT msar.create_schema('foo bar', 'yay') INTO sch_oid;
SELECT msar.create_schema('foo bar', 'yay') ->> 'oid' INTO sch_oid;
RETURN NEXT has_schema('foo bar');
RETURN NEXT is(sch_oid, msar.get_schema_oid('foo bar'));
RETURN NEXT is(obj_description(sch_oid), 'yay');
Expand All @@ -1210,7 +1210,7 @@ $$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION test_create_schema_that_already_exists() RETURNS SETOF TEXT AS $t$
DECLARE sch_oid oid;
BEGIN
SELECT msar.create_schema('foo bar') INTO sch_oid;
SELECT msar.create_schema('foo bar') ->> 'oid' INTO sch_oid;
RETURN NEXT throws_ok($$SELECT msar.create_schema('foo bar')$$, '42P06');
RETURN NEXT is(msar.create_schema_if_not_exists('foo bar'), sch_oid);
END;
Expand Down Expand Up @@ -3954,45 +3954,27 @@ $$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION test_get_record_from_table() RETURNS SETOF TEXT AS $$
DECLARE
rel_id oid;
record_2_results jsonb := '[
{
"1": 2,
"2": 34,
"3": "sdflfflsk",
"4": null,
"5": [1, 2, 3, 4]
}
]'::jsonb;
BEGIN
PERFORM __setup_list_records_table();
rel_id := 'atable'::regclass::oid;
RETURN NEXT is(
msar.get_record_from_table(rel_id, 2),
$j${
"count": 1,
"results": [
{"1": 2, "2": 34, "3": "sdflfflsk", "4": null, "5": [1, 2, 3, 4]}
],
"grouping": null,
"linked_record_summaries": null,
"record_summaries": null
}$j$ || jsonb_build_object(
'query', concat(
'SELECT msar.format_data(id) AS "1", msar.format_data(col1) AS "2",',
' msar.format_data(col2) AS "3", msar.format_data(col3) AS "4",',
' msar.format_data(col4) AS "5" FROM public.atable WHERE (id) = (''2'')',
' ORDER BY "1" ASC LIMIT NULL OFFSET NULL'
)
)
);
RETURN NEXT is(
msar.get_record_from_table(rel_id, 200),
$j${
"count": 0,
"results": [],
"grouping": null,
"linked_record_summaries": null,
"record_summaries": null
}$j$ || jsonb_build_object(
'query', concat(
'SELECT msar.format_data(id) AS "1", msar.format_data(col1) AS "2",',
' msar.format_data(col2) AS "3", msar.format_data(col3) AS "4",',
' msar.format_data(col4) AS "5" FROM public.atable WHERE (id) = (''200'')',
' ORDER BY "1" ASC LIMIT NULL OFFSET NULL'
)
)
);

-- We should be able to retrieve a single record
RETURN NEXT is(msar.get_record_from_table(rel_id, 2) -> 'results', record_2_results);

-- We should be able to retrieve a record via stringified primary key
RETURN NEXT is(msar.get_record_from_table(rel_id, '2') -> 'results', record_2_results);

-- We should get an empty array if the record does not exist
RETURN NEXT is(msar.get_record_from_table(rel_id, 200) -> 'results', '[]'::jsonb);
END;
$$ LANGUAGE plpgsql;

Expand Down
8 changes: 6 additions & 2 deletions db/tables/operations/split.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,15 @@ def split_table(
extracted_table_name,
relationship_fk_column_name=None
):
exec_msar_func(
extracted_table_oid, new_fkey_attnum = exec_msar_func(
conn,
'extract_columns_from_table',
old_table_oid,
extracted_column_attnums,
extracted_table_name,
relationship_fk_column_name
)
).fetchone()[0]
return {
'extracted_table_oid': extracted_table_oid,
'new_fkey_attnum': new_fkey_attnum
}
2 changes: 2 additions & 0 deletions docs/docs/api/rpc.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ To use an RPC function:
options:
members:
- list_
- get
- add
- delete
- patch
Expand Down Expand Up @@ -285,6 +286,7 @@ To use an RPC function:
- split_table
- move_columns
- MappingColumn
- SplitTableInfo

## Responses

Expand Down
20 changes: 18 additions & 2 deletions mathesar/rpc/data_modeling.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,19 @@ def suggest_types(*, table_oid: int, database_id: int, **kwargs) -> dict:
return infer_types.infer_table_column_data_types(conn, table_oid)


class SplitTableInfo(TypedDict):
"""
Information about a table, created from column extraction.
Attributes:
extracted_table_oid: The OID of the table that is created from column extraction.
new_fkey_attnum: The attnum of the newly created foreign key column
referring the extracted_table on the original table.
"""
extracted_table_oid: int
new_fkey_attnum: int


@rpc_method(name="data_modeling.split_table")
@http_basic_auth_login_required
@handle_rpc_exceptions
Expand All @@ -117,7 +130,7 @@ def split_table(
database_id: int,
relationship_fk_column_name: str = None,
**kwargs
) -> None:
) -> SplitTableInfo:
"""
Extract columns from a table to create a new table, linked by a foreign key.
Expand All @@ -127,10 +140,13 @@ def split_table(
extracted_table_name: The name of the new table to be made from the extracted columns.
database_id: The Django id of the database containing the table.
relationship_fk_column_name: The name to give the new foreign key column in the remainder table (optional)
Returns:
The SplitTableInfo object describing the details for the created table as a result of column extraction.
"""
user = kwargs.get(REQUEST_KEY).user
with connect(database_id, user) as conn:
split.split_table(
return split.split_table(
conn,
table_oid,
column_attnums,
Expand Down
33 changes: 28 additions & 5 deletions mathesar/rpc/schemas/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from db.constants import INTERNAL_SCHEMAS
from db.schemas.operations.create import create_schema
from db.schemas.operations.select import list_schemas
from db.schemas.operations.select import list_schemas, get_schema
from db.schemas.operations.drop import drop_schema_via_oid
from db.schemas.operations.alter import patch_schema
from mathesar.rpc.exceptions.handlers import handle_rpc_exceptions
Expand Down Expand Up @@ -58,7 +58,7 @@ def add(
database_id: int,
description: Optional[str] = None,
**kwargs,
) -> int:
) -> SchemaInfo:
"""
Add a schema
Expand All @@ -68,7 +68,7 @@ def add(
description: A description of the schema
Returns:
The integer OID of the schema created
The SchemaInfo describing the user-defined schema in the database.
"""
with connect(database_id, kwargs.get(REQUEST_KEY).user) as conn:
return create_schema(
Expand Down Expand Up @@ -98,6 +98,26 @@ def list_(*, database_id: int, **kwargs) -> list[SchemaInfo]:
return [s for s in schemas if s['name'] not in INTERNAL_SCHEMAS]


@rpc_method(name="schemas.get")
@http_basic_auth_login_required
@handle_rpc_exceptions
def get(*, schema_oid: int, database_id: int, **kwargs) -> SchemaInfo:
"""
Get information about a schema in a database.
Args:
schema_oid: The OID of the schema to get.
database_id: The Django id of the database containing the table.
Returns:
The SchemaInfo describing the user-defined schema in the database.
"""
user = kwargs.get(REQUEST_KEY).user
with connect(database_id, user) as conn:
schema_info = get_schema(conn)
return schema_info


@rpc_method(name="schemas.delete")
@http_basic_auth_login_required
@handle_rpc_exceptions
Expand All @@ -116,14 +136,17 @@ def delete(*, schema_oid: int, database_id: int, **kwargs) -> None:
@rpc_method(name="schemas.patch")
@http_basic_auth_login_required
@handle_rpc_exceptions
def patch(*, schema_oid: int, database_id: int, patch: SchemaPatch, **kwargs) -> None:
def patch(*, schema_oid: int, database_id: int, patch: SchemaPatch, **kwargs) -> SchemaInfo:
"""
Patch a schema, given its OID.
Args:
schema_oid: The OID of the schema to delete.
database_id: The Django id of the database containing the schema.
patch: A SchemaPatch object containing the fields to update.
Returns:
The SchemaInfo describing the user-defined schema in the database.
"""
with connect(database_id, kwargs.get(REQUEST_KEY).user) as conn:
patch_schema(schema_oid, conn, patch)
return patch_schema(schema_oid, conn, patch)
5 changes: 5 additions & 0 deletions mathesar/tests/rpc/test_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,11 @@
"schemas.list",
[user_is_authenticated]
),
(
schemas.get,
"schemas.get",
[user_is_authenticated]
),
(
schemas.patch,
"schemas.patch",
Expand Down
Loading

0 comments on commit e487813

Please sign in to comment.