-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Core: Make namespace separator configurable #10877
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
base: main
Are you sure you want to change the base?
Changes from all commits
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 |
---|---|---|
|
@@ -28,15 +28,13 @@ | |
import org.apache.iceberg.relocated.com.google.common.base.Joiner; | ||
import org.apache.iceberg.relocated.com.google.common.base.Preconditions; | ||
import org.apache.iceberg.relocated.com.google.common.base.Splitter; | ||
import org.apache.iceberg.relocated.com.google.common.base.Strings; | ||
import org.apache.iceberg.relocated.com.google.common.collect.ImmutableMap; | ||
import org.apache.iceberg.relocated.com.google.common.collect.Iterables; | ||
import org.apache.iceberg.relocated.com.google.common.collect.Maps; | ||
|
||
public class RESTUtil { | ||
private static final String NAMESPACE_ESCAPED_SEPARATOR = "%1F"; | ||
private static final Joiner NAMESPACE_ESCAPED_JOINER = Joiner.on(NAMESPACE_ESCAPED_SEPARATOR); | ||
private static final Splitter NAMESPACE_ESCAPED_SPLITTER = | ||
Splitter.on(NAMESPACE_ESCAPED_SEPARATOR); | ||
static final String NAMESPACE_ESCAPED_SEPARATOR = "%1F"; | ||
|
||
/** | ||
* @deprecated since 1.7.0, will be made private in 1.8.0; use {@link | ||
|
@@ -194,15 +192,35 @@ public static String decodeString(String encoded) { | |
* @return UTF-8 encoded string representing the namespace, suitable for use as a URL parameter | ||
*/ | ||
public static String encodeNamespace(Namespace ns) { | ||
Preconditions.checkArgument(ns != null, "Invalid namespace: null"); | ||
String[] levels = ns.levels(); | ||
return encodeNamespace(ns, NAMESPACE_ESCAPED_SEPARATOR); | ||
} | ||
|
||
/** | ||
* Returns a String representation of a namespace that is suitable for use in a URL / URI. | ||
nastra marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* | ||
* <p>This function needs to be called when a namespace is used as a path variable (or query | ||
* parameter etc.), to format the namespace per the spec. | ||
nastra marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* | ||
* <p>{@link RESTUtil#decodeNamespace(String, String)} should be used to parse the namespace from | ||
* a URL parameter. | ||
* | ||
* @param namespace namespace to encode | ||
* @param separator The namespace separator to be used for encoding. The separator will be used | ||
* as-is and won't be UTF-8 encoded. | ||
* @return UTF-8 encoded string representing the namespace, suitable for use as a URL parameter | ||
nastra marked this conversation as resolved.
Show resolved
Hide resolved
nastra marked this conversation as resolved.
Show resolved
Hide resolved
|
||
*/ | ||
public static String encodeNamespace(Namespace namespace, String separator) { | ||
Preconditions.checkArgument(namespace != null, "Invalid namespace: null"); | ||
Preconditions.checkArgument( | ||
!Strings.isNullOrEmpty(separator), "Invalid separator: null or empty"); | ||
String[] levels = namespace.levels(); | ||
String[] encodedLevels = new String[levels.length]; | ||
|
||
for (int i = 0; i < levels.length; i++) { | ||
encodedLevels[i] = encodeString(levels[i]); | ||
} | ||
|
||
return NAMESPACE_ESCAPED_JOINER.join(encodedLevels); | ||
return Joiner.on(separator).join(encodedLevels); | ||
nastra marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
/** | ||
|
@@ -215,8 +233,32 @@ public static String encodeNamespace(Namespace ns) { | |
* @return a namespace | ||
*/ | ||
public static Namespace decodeNamespace(String encodedNs) { | ||
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. This function looks dangerous now. 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. can you please elaborate what you mean by "dangerous" here? Nothing really changed for callers of this method 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. Mixing this and the new function. 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. it's still not clear to my why that would be "dangerous". There is no behavioral change for existing callers of this method. Can you please add a concrete example/explanation to justify the "dangerous" part? 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. Users can mix both - causing "confusion". 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. I don't think that argument is actually true. Users won't be using this code as this code is used on the client and the server but not by users in the classical sense of a user that uses Iceberg. For engine/catalog implementers the javadoc states that one should have used |
||
Preconditions.checkArgument(encodedNs != null, "Invalid namespace: null"); | ||
String[] levels = Iterables.toArray(NAMESPACE_ESCAPED_SPLITTER.split(encodedNs), String.class); | ||
return decodeNamespace(encodedNs, NAMESPACE_ESCAPED_SEPARATOR); | ||
} | ||
|
||
/** | ||
* Takes in a string representation of a namespace as used for a URL parameter and returns the | ||
nastra marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* corresponding namespace. | ||
* | ||
* <p>See also {@link #encodeNamespace} for generating correctly formatted URLs. | ||
* | ||
* @param encodedNamespace a namespace to decode | ||
* @param separator The namespace separator to be used as-is for decoding. This should be the same | ||
* separator that was used when calling {@link RESTUtil#encodeNamespace(Namespace, String)} | ||
* @return a namespace | ||
*/ | ||
public static Namespace decodeNamespace(String encodedNamespace, String separator) { | ||
Preconditions.checkArgument(encodedNamespace != null, "Invalid namespace: null"); | ||
Preconditions.checkArgument( | ||
!Strings.isNullOrEmpty(separator), "Invalid separator: null or empty"); | ||
|
||
// use legacy splitter for backwards compatibility in case an old clients encoded the namespace | ||
// with %1F | ||
Splitter splitter = | ||
encodedNamespace.contains(NAMESPACE_ESCAPED_SEPARATOR) | ||
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. How can this trigger if 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. can you elaborate please what you mean exactly here? |
||
? NAMESPACE_SPLITTER | ||
: Splitter.on(separator); | ||
String[] levels = Iterables.toArray(splitter.split(encodedNamespace), String.class); | ||
|
||
// Decode levels in place | ||
for (int i = 0; i < levels.length; i++) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -75,6 +75,7 @@ | |
/** Adaptor class to translate REST requests into {@link Catalog} API calls. */ | ||
public class RESTCatalogAdapter implements RESTClient { | ||
private static final Splitter SLASH = Splitter.on('/'); | ||
private static final String NAMESPACE_SEPARATOR = "%2E"; | ||
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. Why is this not 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.
|
||
|
||
private static final Map<Class<? extends Exception>, Integer> EXCEPTION_ERROR_CODES = | ||
ImmutableMap.<Class<? extends Exception>, Integer>builder() | ||
|
@@ -292,6 +293,7 @@ public <T extends RESTResponse> T handleRequest( | |
Arrays.stream(Route.values()) | ||
.map(r -> Endpoint.create(r.method.name(), r.resourcePath)) | ||
.collect(Collectors.toList())) | ||
.withOverride(RESTSessionCatalog.NAMESPACE_SEPARATOR, NAMESPACE_SEPARATOR) | ||
.build()); | ||
|
||
case LIST_NAMESPACES: | ||
|
@@ -675,7 +677,7 @@ public static void configureResponseFromException( | |
} | ||
|
||
private static Namespace namespaceFromPathVars(Map<String, String> pathVars) { | ||
return RESTUtil.decodeNamespace(pathVars.get("namespace")); | ||
return RESTUtil.decodeNamespace(pathVars.get("namespace"), NAMESPACE_SEPARATOR); | ||
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. I thought that @jackye1995 suggested sending the separator from the client each time. Is that not what we want to do here? 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. yes and that is being handled in #10905. However, as a first step we need to make the namespace separator configurable (regardless of whether it's configurable from the server or the client), which is being handled in this PR. Making it controllable from the client and send a query param is handled after this PR in #10905 (and requires a vote on the spec change) |
||
} | ||
|
||
private static TableIdentifier tableIdentFromPathVars(Map<String, String> pathVars) { | ||
|
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.
This function looks dangerous now.
Uh oh!
There was an error while loading. Please reload this page.
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.
can you please elaborate what you mean by "dangerous" here? Nothing really changed for callers of this method (other than the fact that a
Joiner
is created every time this method is called)