Skip to content

Commit

Permalink
Merge pull request #3740 from mathesar-foundation/records_get
Browse files Browse the repository at this point in the history
Add `records.get` RPC function
  • Loading branch information
Anish9901 authored Aug 7, 2024
2 parents 1f5b940 + 487e6af commit 86e817a
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 2 deletions.
23 changes: 23 additions & 0 deletions db/records/operations/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,29 @@ def list_records_from_table(
return result


def get_record_from_table(
conn,
record_id,
table_oid,
):
"""
Get single record from a table by its primary key
Only data from which the user is granted `SELECT` is returned.
Args:
record_id: The primary key value of the record.
table_id: The OID of the table whose record we'll get.
"""
result = db_conn.exec_msar_func(
conn,
'get_record_from_table',
table_oid,
record_id,
).fetchone()[0]
return result


def search_records_from_table(
conn,
table_oid,
Expand Down
27 changes: 25 additions & 2 deletions db/sql/00_msar.sql
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,7 @@ Return the first column attnum in the primary key of a given relation (e.g., tab
Args:
rel_id: The OID of the relation.
*/
SELECT conkey[1]
SELECT CASE WHEN array_length(conkey, 1) = 1 THEN conkey[1] END
FROM pg_constraint
WHERE contype='p'
AND conrelid=rel_id;
Expand All @@ -586,7 +586,7 @@ Args:
sch_name: The schema of the relation, unquoted.
rel_name: The name of the relation, unqualified and unquoted.
*/
SELECT conkey[1]
SELECT CASE WHEN array_length(conkey, 1) = 1 THEN conkey[1] END
FROM pg_constraint
WHERE contype='p'
AND conrelid=msar.get_relation_oid(sch_name, rel_name);
Expand Down Expand Up @@ -3714,3 +3714,26 @@ BEGIN
RETURN records;
END;
$$ LANGUAGE plpgsql;


CREATE OR REPLACE FUNCTION
msar.get_record_from_table(tab_id oid, rec_id anyelement) RETURNS jsonb AS $$/*
Get single record from a table. Only columns to which the user has access are returned.
Args:
tab_id: The OID of the table whose record we'll get.
rec_id: The id value of the record.
The table must have a single primary key column.
*/
SELECT msar.list_records_from_table(
tab_id, null, null, null,
jsonb_build_object(
'type', 'equal', 'args', jsonb_build_array(
jsonb_build_object('type', 'attnum', 'value', msar.get_pk_column(tab_id)),
jsonb_build_object('type', 'literal', 'value', rec_id)
)
),
null
)
$$ LANGUAGE SQL STABLE RETURNS NULL ON NULL INPUT;
42 changes: 42 additions & 0 deletions db/sql/test_00_msar.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3696,3 +3696,45 @@ BEGIN
RETURN NEXT is((search_result -> 'count')::integer, 3);
END;
$$ LANGUAGE plpgsql;


CREATE OR REPLACE FUNCTION test_get_record_from_table() RETURNS SETOF TEXT AS $$
DECLARE
rel_id oid;
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
}$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
}$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'
)
)
);
END;
$$ LANGUAGE plpgsql;
1 change: 1 addition & 0 deletions docs/docs/api/rpc.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ To use an RPC function:
options:
members:
- list_
- get
- search
- RecordList
- OrderBy
Expand Down
31 changes: 31 additions & 0 deletions mathesar/rpc/records.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,37 @@ def list_(
return RecordList.from_dict(record_info)


@rpc_method(name="records.get")
@http_basic_auth_login_required
@handle_rpc_exceptions
def get(
*,
record_id: Any,
table_oid: int,
database_id: int,
**kwargs
) -> RecordList:
"""
Get single record from a table by its primary key.
Args:
record_id: The primary key value of the record to be gotten.
table_oid: Identity of the table in the user's database.
database_id: The Django id of the database containing the table.
Returns:
The requested record, along with some metadata.
"""
user = kwargs.get(REQUEST_KEY).user
with connect(database_id, user) as conn:
record_info = record_select.get_record_from_table(
conn,
record_id,
table_oid,
)
return RecordList.from_dict(record_info)


@rpc_method(name="records.search")
@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 @@ -149,6 +149,11 @@
"records.list",
[user_is_authenticated]
),
(
records.get,
"records.get",
[user_is_authenticated]
),
(
records.search,
"records.search",
Expand Down
48 changes: 48 additions & 0 deletions mathesar/tests/rpc/test_records.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,54 @@ def mock_list_records(
assert actual_records_list == expect_records_list


def test_records_get(rf, monkeypatch):
username = 'alice'
password = 'pass1234'
table_oid = 23457
database_id = 2
record_id = 2342
request = rf.post('/api/rpc/v0/', data={})
request.user = User(username=username, password=password)

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

def mock_get_record(
conn,
_record_id,
_table_oid,
):
if _table_oid != table_oid or _record_id != record_id:
raise AssertionError('incorrect parameters passed')
return {
"count": 1,
"results": [{"1": "abcde", "2": 12345}, {"1": "fghij", "2": 67890}],
"query": 'SELECT mycol AS "1", anothercol AS "2" FROM mytable LIMIT 2',
"grouping": None,
}

monkeypatch.setattr(records, 'connect', mock_connect)
monkeypatch.setattr(records.record_select, 'get_record_from_table', mock_get_record)
expect_record = {
"count": 1,
"results": [{"1": "abcde", "2": 12345}, {"1": "fghij", "2": 67890}],
"grouping": None,
"preview_data": [],
"query": 'SELECT mycol AS "1", anothercol AS "2" FROM mytable LIMIT 2',
}
actual_record = records.get(
record_id=record_id, table_oid=table_oid, database_id=database_id, request=request
)
assert actual_record == expect_record


def test_records_search(rf, monkeypatch):
username = 'alice'
password = 'pass1234'
Expand Down

0 comments on commit 86e817a

Please sign in to comment.