Repository navigation
REST: Don't URL-encode view names in create_view and register_view bodies #4151
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
Changes from all commits
9fbb1e8
abb82bf
3309a18
83e6196
4677321
6af5063
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -1312,7 +1312,7 @@ def create_view( | |||||||||||||||||||
| location = location.rstrip("/") | ||||||||||||||||||||
|
|
||||||||||||||||||||
| request = CreateViewRequest( | ||||||||||||||||||||
| name=namespace_and_view["view"], | ||||||||||||||||||||
| name=self._identifier_to_validated_tuple(identifier)[-1], | ||||||||||||||||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use mirrors iceberg-python/pyiceberg/catalog/rest/__init__.py Lines 1230 to 1238 in 12d4a9a
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Idea (not necessarily for this PR): what if Identifier still acted like a tuple but had named methods on it as well? I really don't like the -1 here, since it's not always intuitive why we're doing that. Could you add a comment explaining what you're trying to do? |
||||||||||||||||||||
| location=location, | ||||||||||||||||||||
| view_schema=fresh_schema, | ||||||||||||||||||||
| view_version=view_version, | ||||||||||||||||||||
|
|
@@ -1826,15 +1826,14 @@ def view_exists(self, identifier: str | Identifier) -> bool: | |||||||||||||||||||
| def register_view(self, identifier: str | Identifier, metadata_location: str) -> View: | ||||||||||||||||||||
| self._check_endpoint(Capability.V1_REGISTER_VIEW) | ||||||||||||||||||||
| namespace_and_view = self._split_identifier_for_path(identifier, IdentifierKind.VIEW) | ||||||||||||||||||||
| namespace = namespace_and_view["namespace"] | ||||||||||||||||||||
| view = namespace_and_view["view"] | ||||||||||||||||||||
| 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"]), | ||||||||||||||||||||
|
Comment on lines
+1829
to
+1836
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. follows register_table iceberg-python/pyiceberg/catalog/rest/__init__.py Lines 1352 to 1360 in 12d4a9a
|
||||||||||||||||||||
| data=serialized_json, | ||||||||||||||||||||
| ) | ||||||||||||||||||||
| try: | ||||||||||||||||||||
|
|
||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -719,6 +719,53 @@ def test_rest_drop_view( | |
| assert not rest_catalog.view_exists(identifier) | ||
|
|
||
|
|
||
| @pytest.mark.integration | ||
| def test_rest_identifiers_with_special_characters( | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. integration test that checks special character handling on relevant endpoints |
||
| rest_catalog: RestCatalog, table_schema_simple: Schema, example_view_metadata_v1: dict[str, Any] | ||
| ) -> None: | ||
| """Names that need URL encoding must round-trip unchanged through path, query, and body parameters.""" | ||
| # "%" is omitted because the REST fixture's Jetty rejects "%25" in URL paths ("400 Ambiguous URI path encoding") | ||
| for name in ["with space", "a+b&c=d", "\u65e5\u672c"]: | ||
| parent = (f"{name}_{uuid.uuid4().hex}",) | ||
| namespace = (*parent, name) | ||
| table_identifier = (*namespace, f"table {name}") | ||
| view_identifier = (*namespace, f"view {name}") | ||
| registered_view_identifier = (*namespace, f"registered view {name}") | ||
|
|
||
| rest_catalog.create_namespace(parent) | ||
| rest_catalog.create_namespace(namespace, properties={"key": name}) | ||
| assert parent in rest_catalog.list_namespaces() | ||
| assert rest_catalog.list_namespaces(parent) == [namespace] | ||
| assert rest_catalog.namespace_exists(namespace) | ||
| assert rest_catalog.load_namespace_properties(namespace)["key"] == name | ||
|
|
||
| rest_catalog.create_table(table_identifier, table_schema_simple) | ||
| assert rest_catalog.table_exists(table_identifier) | ||
| assert rest_catalog.list_tables(namespace) == [table_identifier] | ||
| table = rest_catalog.load_table(table_identifier) | ||
| assert table.name() == table_identifier | ||
| with table.transaction() as tx: | ||
| tx.set_properties(key=name) | ||
| assert rest_catalog.load_table(table_identifier).properties["key"] == name | ||
|
|
||
| view = View(view_identifier, ViewMetadata.model_validate(example_view_metadata_v1)) | ||
| created_view = 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] | ||
|
|
||
| assert created_view.metadata_location is not None | ||
| rest_catalog.register_view(registered_view_identifier, created_view.metadata_location) | ||
| assert rest_catalog.view_exists(registered_view_identifier) | ||
| assert sorted(rest_catalog.list_views(namespace)) == sorted([view_identifier, registered_view_identifier]) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. two views here because one is from |
||
|
|
||
| rest_catalog.drop_view(registered_view_identifier) | ||
| rest_catalog.drop_view(view_identifier) | ||
| rest_catalog.drop_table(table_identifier) | ||
| rest_catalog.drop_namespace(namespace) | ||
| rest_catalog.drop_namespace(parent) | ||
| assert not rest_catalog.namespace_exists(parent) | ||
|
|
||
|
|
||
| @pytest.mark.integration | ||
| def test_rest_custom_namespace_separator(rest_catalog: RestCatalog, table_schema_simple: Schema) -> None: | ||
| """ | ||
|
|
||
There was a problem hiding this comment.
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).
("ns", "my view")_identifier_to_validated_tuple(identifier)("ns", "my view")NoSuchIdentifierErrorif there's no namespace_split_identifier_for_path(identifier, kind){"namespace": "ns", "view": "my%20view"}