Skip to content

REST: Don't URL-encode view names in create_view and register_view bodies - #4151

Open
kevinjqliu wants to merge 6 commits into
apache:mainfrom
kevinjqliu:kevinjqliu-fix-rest-view-name-encoding
Open

kevinjqliu wants to merge 6 commits into
apache:mainfrom
kevinjqliu:kevinjqliu-fix-rest-view-name-encoding

Conversation

@kevinjqliu

@kevinjqliu kevinjqliu commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Rationale for this change

Same kind of bug as #4112, but for views. create_view and register_view put the URL-encoded view name in the JSON body, so my view gets created as my%20view. Use the raw name instead, like create_table and register_table.

Also added an integration test that round-trips names with spaces, +&=, and non-ASCII through the REST fixture.

Are these changes tested?

Yes, both fail on main:

  • pytest tests/catalog/test_rest.py: passed
  • pytest tests/integration/test_catalog.py -m integration -k test_rest_identifiers_with_special_characters: passed

Are there any user-facing changes?

Yes, bug fix for view names that need URL encoding.

AI Disclosure: Assisted by GitHub Copilot.

kevinjqliu and others added 5 commits October 24, 2026 16:18
create_view and register_view took the view name from
_split_identifier_for_path, which URL-quotes it for use as a path
segment, and put it in the JSON request body. A view named "my view"
was created as "my%20view". Use the raw name, as create_table and
register_table already do.

Co-authored-by: Copilot App <[email protected]>
Round-trip namespace, table, and view names containing spaces, reserved
URL characters, and non-ASCII text through path, query, and body
parameters. This catches encoding mistakes like the double-encoded
list_namespaces parent and the encoded view name in create_view.

Co-authored-by: Copilot App <[email protected]>
commit_table is the only caller of the TableIdentifier branch of
_split_identifier_for_path, so table updates had no coverage for names
that need URL encoding.

Co-authored-by: Copilot App <[email protected]>
Matches create_view and register_table: path from _split_identifier_for_path, raw name for the body.

Co-authored-by: Copilot App <[email protected]>


@pytest.mark.integration
def test_rest_identifiers_with_special_characters(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

integration test that checks special character handling on relevant endpoints


request = CreateViewRequest(
name=namespace_and_view["view"],
name=self._identifier_to_validated_tuple(identifier)[-1],

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

use _identifier_to_validated_tuple for request bodies so names aren't escaped, and escape only in URL paths.

mirrors CreateTableRequest

request = CreateTableRequest(
name=self._identifier_to_validated_tuple(identifier)[-1],
location=location,
table_schema=fresh_schema,
partition_spec=fresh_partition_spec,
write_order=fresh_sort_order,
stage_create=stage_create,
properties=properties,
)

Comment on lines +1828 to +1835
identifier_tuple = self._identifier_to_validated_tuple(identifier)
if self.table_exists(identifier):
raise TableAlreadyExistsError(f"Table {namespace}.{view} already exists")
raise TableAlreadyExistsError(f"Table {'.'.join(identifier_tuple)} already exists")

request = RegisterViewRequest(name=view, metadata_location=metadata_location)
request = RegisterViewRequest(name=identifier_tuple[-1], metadata_location=metadata_location)
serialized_json = request.model_dump_json().encode(UTF8)
response = self._session.post(
self.url(Endpoints.register_view, namespace=namespace),
self.url(Endpoints.register_view, namespace=namespace_and_view["namespace"]),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

follows register_table

namespace_and_table = self._split_identifier_for_path(identifier)
request = RegisterTableRequest(
name=self._identifier_to_validated_tuple(identifier)[-1],
metadata_location=metadata_location,
overwrite=overwrite,
)
serialized_json = request.model_dump_json().encode(UTF8)
response = self._session.post(
self.url(Endpoints.register_table, namespace=namespace_and_table["namespace"]),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The bug was using the wrong helper in the request body. The body should not be escaped, the URL should be. We should probably do some refactoring here (esp. with the naming).

Helper Escaped? Returns for ("ns", "my view") Use for
_identifier_to_validated_tuple(identifier) No ("ns", "my view") JSON body, error messages. Raises NoSuchIdentifierError if there's no namespace
_split_identifier_for_path(identifier, kind) Yes {"namespace": "ns", "view": "my%20view"} URL path only

@kevinjqliu kevinjqliu changed the title REST: Send raw view name in create_view and register_view REST: Don't URL-encode view names in create_view and register_view bodies Oct 25, 2026
@kevinjqliu
kevinjqliu marked this pull request as ready for review October 25, 2026 00:46
Copilot AI balanced review requested due to automatic review settings October 25, 2026 00:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The focused fix preserves path encoding, covers both affected request bodies with regression tests, and has no identified blocking issues.

No open issues 🎉

What changed in this PR

Fixes REST view creation and registration so names containing special characters remain unchanged in JSON request bodies, while URL paths retain encoding.

Changes:

  • Uses raw view names in create_view and register_view payloads.
  • Adds regression tests for both payloads and integration coverage for special-character identifiers.
File Description
tests/​integration/​test_catalog.py Adds REST round-trip checks for namespaces, tables, and views with special characters.
tests/​catalog/​test_rest.py Verifies create and register view requests preserve raw names in JSON bodies.
pyiceberg/​catalog/​rest/​__init__.py Separates raw view names from encoded paths and uses readable identifiers in conflict errors.

Review effort: Balanced

rest_catalog.create_view(view_identifier, view.schema(), view.current_version())
assert rest_catalog.view_exists(view_identifier)
assert rest_catalog.list_views(namespace) == [view_identifier]
# TODO: Cover register_view once View exposes its metadata location (#4088)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

we should add register_view here, but its not yet merged

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants