snazy commented on code in PR #8857: URL: https://github.com/apache/iceberg/pull/8857#discussion_r1365303813
########## nessie/src/main/java/org/apache/iceberg/nessie/NessieIcebergClient.java: ########## @@ -223,27 +284,57 @@ namespace, getRef().getName()), } public boolean dropNamespace(Namespace namespace) throws NamespaceNotEmptyException { + + getRef().checkMutable(); + ContentKey key = ContentKey.of(namespace.levels()); + try { - getRef().checkMutable(); - withReference( - getApi() - .deleteNamespace() - .namespace(org.projectnessie.model.Namespace.of(namespace.levels()))) - .delete(); - refresh(); + + CommitMultipleOperationsBuilder commitBuilder = + api.commitMultipleOperations() + .commitMeta(NessieUtil.buildCommitMetadata("drop namespace " + key, catalogOptions)) + .operation(Operation.Delete.of(key)); + + Tasks.foreach(commitBuilder) + .retry(5) + .stopRetryOn( + NessieReferenceNotFoundException.class, NessieReferenceConflictException.class) + .throwFailureWhenFinished() + .onFailure((o, exception) -> refresh()) + .run( + b -> { + Branch branch = b.branch((Branch) getReference()).commit(); + getRef().updateReference(branch); + }, + BaseNessieClientServerException.class); + return true; - } catch (NessieNamespaceNotFoundException e) { - return false; - } catch (NessieNotFoundException e) { + } catch (NessieReferenceNotFoundException e) { LOG.error( "Cannot drop Namespace '{}': ref '{}' is no longer valid.", namespace, getRef().getName(), e); return false; - } catch (NessieNamespaceNotEmptyException e) { - throw new NamespaceNotEmptyException( - e, "Namespace '%s' is not empty. One or more tables exist.", namespace); + } catch (NessieReferenceConflictException e) { + List<Conflict> conflicts = e.getErrorDetails().conflicts(); Review Comment: Ah, yea - it's nullable ########## nessie/src/main/java/org/apache/iceberg/nessie/NessieIcebergClient.java: ########## @@ -181,133 +186,207 @@ public IcebergTable table(TableIdentifier tableIdentifier) { } public void createNamespace(Namespace namespace, Map<String, String> metadata) { + getRef().checkMutable(); + if (namespace.isEmpty()) { + throw new IllegalArgumentException("Creating empty namespaces is not supported"); + } + ContentKey key = ContentKey.of(namespace.levels()); + org.projectnessie.model.Namespace content = + org.projectnessie.model.Namespace.of(key.getElements(), metadata); try { - getRef().checkMutable(); - withReference( - getApi() - .createNamespace() - .namespace(org.projectnessie.model.Namespace.of(namespace.levels())) - .properties(metadata)) - .create(); - refresh(); - } catch (NessieNamespaceAlreadyExistsException e) { - throw new AlreadyExistsException(e, "Namespace already exists: %s", namespace); + Map<ContentKey, Content> contentMap = + api.getContent().reference(getReference()).key(key).get(); + Content existing = contentMap.get(key); + if (existing != null) { + throw namespaceAlreadyExists(key, existing, null); + } + try { + commitRetry("create namespace " + key, Operation.Put.of(key, content)); + } catch (NessieReferenceConflictException e) { + Optional<Conflict> conflict = NessieUtil.extractSingleConflict(e); + if (conflict.isPresent()) { + switch (conflict.get().conflictType()) { + case KEY_EXISTS: + Content conflicting = withReference(api.getContent()).key(key).get().get(key); + throw namespaceAlreadyExists(key, conflicting, e); + case NAMESPACE_ABSENT: + throw new NoSuchNamespaceException( + e, + "Cannot create Namespace '%s': parent namespace '%s' does not exist", + namespace, + conflict.get().key()); + } + } + throw new RuntimeException( + String.format("Cannot create Namespace '%s': %s", namespace, e.getMessage())); + } } catch (NessieNotFoundException e) { throw new RuntimeException( String.format( - "Cannot create Namespace '%s': " + "ref '%s' is no longer valid.", + "Cannot create Namespace '%s': ref '%s' is no longer valid.", namespace, getRef().getName()), e); + } catch (BaseNessieClientServerException e) { + throw new RuntimeException( + String.format("Cannot create Namespace '%s': %s", namespace, e.getMessage()), e); } } public List<Namespace> listNamespaces(Namespace namespace) throws NoSuchNamespaceException { try { - GetNamespacesResponse response = - withReference( - getApi() - .getMultipleNamespaces() - .namespace(org.projectnessie.model.Namespace.of(namespace.levels()))) - .get(); - return response.getNamespaces().stream() - .map(ns -> Namespace.of(ns.getElements().toArray(new String[0]))) - .filter(ns -> ns.length() == namespace.length() + 1) + org.projectnessie.model.Namespace root = + org.projectnessie.model.Namespace.of(namespace.levels()); + String filter = + namespace.isEmpty() + ? "size(entry.keyElements) == 1" + : String.format( + "size(entry.keyElements) == %d && entry.encodedKey.startsWith('%s.')", + root.getElementCount() + 1, root.name()); + List<ContentKey> entries = + withReference(api.getEntries()).filter(filter).stream() + .filter(e -> Content.Type.NAMESPACE.equals(e.getType())) + .map(EntriesResponse.Entry::getName) + .collect(Collectors.toList()); + if (entries.isEmpty()) { + return Collections.emptyList(); + } + GetContentBuilder getContent = withReference(api.getContent()); + entries.forEach(getContent::key); + return getContent.get().values().stream() + .map(v -> v.unwrap(org.projectnessie.model.Namespace.class)) + .filter(Optional::isPresent) + .map(Optional::get) + .map(v -> Namespace.of(v.getElements().toArray(new String[0]))) + .filter(v -> v.length() == namespace.length() + 1) // only direct children Review Comment: This filer is done in CEL (duplicates the CEL-filter) ########## nessie/src/main/java/org/apache/iceberg/nessie/NessieIcebergClient.java: ########## @@ -181,133 +186,207 @@ public IcebergTable table(TableIdentifier tableIdentifier) { } public void createNamespace(Namespace namespace, Map<String, String> metadata) { + getRef().checkMutable(); + if (namespace.isEmpty()) { + throw new IllegalArgumentException("Creating empty namespaces is not supported"); + } + ContentKey key = ContentKey.of(namespace.levels()); + org.projectnessie.model.Namespace content = + org.projectnessie.model.Namespace.of(key.getElements(), metadata); try { - getRef().checkMutable(); - withReference( - getApi() - .createNamespace() - .namespace(org.projectnessie.model.Namespace.of(namespace.levels())) - .properties(metadata)) - .create(); - refresh(); - } catch (NessieNamespaceAlreadyExistsException e) { - throw new AlreadyExistsException(e, "Namespace already exists: %s", namespace); + Map<ContentKey, Content> contentMap = + api.getContent().reference(getReference()).key(key).get(); + Content existing = contentMap.get(key); + if (existing != null) { + throw namespaceAlreadyExists(key, existing, null); + } + try { + commitRetry("create namespace " + key, Operation.Put.of(key, content)); + } catch (NessieReferenceConflictException e) { + Optional<Conflict> conflict = NessieUtil.extractSingleConflict(e); + if (conflict.isPresent()) { + switch (conflict.get().conflictType()) { + case KEY_EXISTS: + Content conflicting = withReference(api.getContent()).key(key).get().get(key); + throw namespaceAlreadyExists(key, conflicting, e); + case NAMESPACE_ABSENT: + throw new NoSuchNamespaceException( + e, + "Cannot create Namespace '%s': parent namespace '%s' does not exist", + namespace, + conflict.get().key()); + } + } + throw new RuntimeException( + String.format("Cannot create Namespace '%s': %s", namespace, e.getMessage())); + } } catch (NessieNotFoundException e) { throw new RuntimeException( String.format( - "Cannot create Namespace '%s': " + "ref '%s' is no longer valid.", + "Cannot create Namespace '%s': ref '%s' is no longer valid.", namespace, getRef().getName()), e); + } catch (BaseNessieClientServerException e) { + throw new RuntimeException( + String.format("Cannot create Namespace '%s': %s", namespace, e.getMessage()), e); } } public List<Namespace> listNamespaces(Namespace namespace) throws NoSuchNamespaceException { try { - GetNamespacesResponse response = - withReference( - getApi() - .getMultipleNamespaces() - .namespace(org.projectnessie.model.Namespace.of(namespace.levels()))) - .get(); - return response.getNamespaces().stream() - .map(ns -> Namespace.of(ns.getElements().toArray(new String[0]))) - .filter(ns -> ns.length() == namespace.length() + 1) + org.projectnessie.model.Namespace root = + org.projectnessie.model.Namespace.of(namespace.levels()); + String filter = + namespace.isEmpty() + ? "size(entry.keyElements) == 1" Review Comment: I think we can let the server do the content-type==NAMESPACE filtering ########## nessie/src/main/java/org/apache/iceberg/nessie/NessieIcebergClient.java: ########## @@ -181,23 +182,83 @@ public IcebergTable table(TableIdentifier tableIdentifier) { } public void createNamespace(Namespace namespace, Map<String, String> metadata) { + getRef().checkMutable(); + + if (namespace.isEmpty()) { + throw new IllegalArgumentException("Creating empty namespaces is not supported"); + } + + ContentKey key = ContentKey.of(namespace.levels()); + org.projectnessie.model.Namespace content = + org.projectnessie.model.Namespace.of(key.getElements(), metadata); + try { - getRef().checkMutable(); - withReference( - getApi() - .createNamespace() - .namespace(org.projectnessie.model.Namespace.of(namespace.levels())) - .properties(metadata)) - .create(); - refresh(); - } catch (NessieNamespaceAlreadyExistsException e) { - throw new AlreadyExistsException(e, "Namespace already exists: %s", namespace); + + // First, check that the namespace doesn't exist at the current hash; + // this will avoid a NessieBadRequestException when committing the namespace creation + Reference ref = getReference(); + Map<ContentKey, Content> contentMap = api.getContent().reference(ref).key(key).get(); Review Comment: Okay, let's keep it then -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: issues-unsubscr...@iceberg.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org --------------------------------------------------------------------- To unsubscribe, e-mail: issues-unsubscr...@iceberg.apache.org For additional commands, e-mail: issues-h...@iceberg.apache.org