This is an automated email from the ASF dual-hosted git repository.

krathbun pushed a commit to branch 2.1
in repository https://gitbox.apache.org/repos/asf/accumulo.git


The following commit(s) were added to refs/heads/2.1 by this push:
     new 1ab1816e42 Fixes some createtable bugs: (#5990)
1ab1816e42 is described below

commit 1ab1816e42ec1647c107d14f4f0af0da26592b69
Author: Kevin Rathbun <[email protected]>
AuthorDate: Fri Dec 5 12:09:26 2025 -0500

    Fixes some createtable bugs: (#5990)
    
    - Fixes bugs with `--copy-config` (`-cc`) and `--no-default-iterators` 
(`-ndi`) options of `createtable` Shell command:
            - Changes to default iterator settings on src table are now carried 
over to dest table using `-cc`
            - `-ndi` option did not work. Tables were still created with 
default iterator props. Now works as expected.
            - Results of using `-cc` and `-ndi` in conjunction were incorrect 
(e.g., copy config of src table (which has default iters) to dest table 
(specify `-ndi`) would result in a table with default iters)
            - Deprecated `-ndi` in favor of new `--no-default-table-props` 
(`-ndtp`) which functions the same, but better describes the functionality.
    - Fixes bugs with `NewTableConfiguration`
            - `NewTableConfiguration` now considers conflicts with default 
iterators and iterators set (either through `attachIterator()` or 
`setProperties()`). Previously could set an iterator with the same priority or 
name as the default iterator (`VersioningIterator`).
            - Deprecated `NewTableConfiguration.withoutDefaultIterators()` in 
favor of new `NewTableConfiguration.withoutDefaults()` which functions the 
same, but better describes the functionality.
    - New tests to verify expected functionality of createtable command with 
`-cc` and `-ndi`, creating tables with same config as another table via Java 
API, and `NewTableConfiguration` with iterator conflicts with default iterators
    
    closes #5976
    
    Co-authored-by: Keith Turner <[email protected]>
---
 .../core/client/admin/NewTableConfiguration.java   | 57 +++++++++++++--
 .../core/client/admin/TableOperations.java         |  4 +-
 .../core/iteratorsImpl/IteratorConfigUtil.java     | 47 ++++++++----
 .../shell/commands/CreateTableCommand.java         | 36 +++++++---
 .../apache/accumulo/test/ConditionalWriterIT.java  |  3 +-
 .../org/apache/accumulo/test/NamespacesIT.java     |  2 +-
 .../accumulo/test/NewTableConfigurationIT.java     | 83 +++++++++++++++++++++-
 .../apache/accumulo/test/TableOperationsIT.java    | 62 ++++++++++++++++
 .../test/replication/CyclicReplicationIT.java      |  4 +-
 .../accumulo/test/shell/ShellCreateTableIT.java    | 74 +++++++++++++++++++
 10 files changed, 332 insertions(+), 40 deletions(-)

diff --git 
a/core/src/main/java/org/apache/accumulo/core/client/admin/NewTableConfiguration.java
 
b/core/src/main/java/org/apache/accumulo/core/client/admin/NewTableConfiguration.java
index 74847a103f..d7ad0ff331 100644
--- 
a/core/src/main/java/org/apache/accumulo/core/client/admin/NewTableConfiguration.java
+++ 
b/core/src/main/java/org/apache/accumulo/core/client/admin/NewTableConfiguration.java
@@ -39,6 +39,7 @@ import org.apache.accumulo.core.client.summary.Summarizer;
 import org.apache.accumulo.core.client.summary.SummarizerConfiguration;
 import org.apache.accumulo.core.clientImpl.TableOperationsHelper;
 import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.data.constraints.DefaultKeySizeConstraint;
 import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
 import org.apache.accumulo.core.iterators.user.VersioningIterator;
 import org.apache.accumulo.core.iteratorsImpl.IteratorConfigUtil;
@@ -48,6 +49,7 @@ import org.apache.accumulo.core.util.LocalityGroupUtil;
 import 
org.apache.accumulo.core.util.LocalityGroupUtil.LocalityGroupConfigurationError;
 import org.apache.hadoop.io.Text;
 
+import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableSortedSet;
 
 /**
@@ -64,7 +66,7 @@ public class NewTableConfiguration {
   private static final InitialTableState DEFAULT_CREATION_MODE = 
InitialTableState.ONLINE;
   private InitialTableState initialTableState = DEFAULT_CREATION_MODE;
 
-  private boolean limitVersion = true;
+  private boolean includeDefaults = true;
 
   private Map<String,String> properties = Collections.emptyMap();
   private Map<String,String> samplerProps = Collections.emptyMap();
@@ -105,10 +107,29 @@ public class NewTableConfiguration {
    * the table to be created without that iterator, or any others which may 
become defaults in the
    * future.
    *
+   * @deprecated since 2.1.4. This method prevents all default table 
properties from being set. This
+   *             includes the default iterator properties as well as the 
default key size constraint
+   *             ({@link DefaultKeySizeConstraint}). Replaced by {@link 
#withoutDefaults()} to match
+   *             name with functionality.
    * @return this
    */
+  @Deprecated(since = "2.1.4")
   public NewTableConfiguration withoutDefaultIterators() {
-    this.limitVersion = false;
+    this.includeDefaults = false;
+    return this;
+  }
+
+  /**
+   * Currently, the default table properties include the default iterator
+   * ({@link VersioningIterator}) and the constraint {@link 
DefaultKeySizeConstraint}. This method
+   * will cause the table to be created without this iterator or constraint, 
and any others which
+   * may become defaults in the future.
+   *
+   * @since 2.1.4
+   * @return this
+   */
+  public NewTableConfiguration withoutDefaults() {
+    this.includeDefaults = false;
     return this;
   }
 
@@ -167,15 +188,39 @@ public class NewTableConfiguration {
   public Map<String,String> getProperties() {
     Map<String,String> propertyMap = new HashMap<>();
 
-    if (limitVersion) {
-      
propertyMap.putAll(IteratorConfigUtil.generateInitialTableProperties(limitVersion));
-    }
-
     propertyMap.putAll(summarizerProps);
     propertyMap.putAll(samplerProps);
     propertyMap.putAll(properties);
     propertyMap.putAll(iteratorProps);
     propertyMap.putAll(localityProps);
+
+    if (includeDefaults) {
+      var initTableProps = IteratorConfigUtil.getInitialTableProperties();
+      // check the properties for conflicts with default iterators
+      var defaultIterSettings = 
IteratorConfigUtil.getInitialTableIteratorSettings();
+      // if a default prop already exists, don't want to consider that a 
conflict
+      var noDefaultsPropMap = new HashMap<>(propertyMap);
+      noDefaultsPropMap.entrySet().removeIf(entry -> 
initTableProps.get(entry.getKey()) != null
+          && initTableProps.get(entry.getKey()).equals(entry.getValue()));
+      defaultIterSettings.forEach((setting, scopes) -> {
+        try {
+          TableOperationsHelper.checkIteratorConflicts(noDefaultsPropMap, 
setting, scopes);
+        } catch (AccumuloException e) {
+          throw new IllegalStateException(e);
+        }
+      });
+
+      // check the properties for conflicts with default properties 
(non-iterator)
+      var nonIterDefaults = IteratorConfigUtil.getInitialTableProperties();
+      
nonIterDefaults.keySet().removeAll(IteratorConfigUtil.getInitialTableIterators().keySet());
+      nonIterDefaults.forEach((dk, dv) -> {
+        var valInPropMap = propertyMap.get(dk);
+        Preconditions.checkState(valInPropMap == null || 
valInPropMap.equals(dv), String.format(
+            "conflict for property %s : %s (default val) != %s (set val)", dk, 
dv, valInPropMap));
+      });
+
+      propertyMap.putAll(initTableProps);
+    }
     return Collections.unmodifiableMap(propertyMap);
   }
 
diff --git 
a/core/src/main/java/org/apache/accumulo/core/client/admin/TableOperations.java 
b/core/src/main/java/org/apache/accumulo/core/client/admin/TableOperations.java
index c0f85c1e2a..b59e9855bd 100644
--- 
a/core/src/main/java/org/apache/accumulo/core/client/admin/TableOperations.java
+++ 
b/core/src/main/java/org/apache/accumulo/core/client/admin/TableOperations.java
@@ -102,7 +102,7 @@ public interface TableOperations {
     if (limitVersion) {
       create(tableName);
     } else {
-      create(tableName, new NewTableConfiguration().withoutDefaultIterators());
+      create(tableName, new NewTableConfiguration().withoutDefaults());
     }
   }
 
@@ -124,7 +124,7 @@ public interface TableOperations {
     if (versioningIter) {
       create(tableName, ntc);
     } else {
-      create(tableName, ntc.withoutDefaultIterators());
+      create(tableName, ntc.withoutDefaults());
     }
   }
 
diff --git 
a/core/src/main/java/org/apache/accumulo/core/iteratorsImpl/IteratorConfigUtil.java
 
b/core/src/main/java/org/apache/accumulo/core/iteratorsImpl/IteratorConfigUtil.java
index 5758411e04..59224deb9b 100644
--- 
a/core/src/main/java/org/apache/accumulo/core/iteratorsImpl/IteratorConfigUtil.java
+++ 
b/core/src/main/java/org/apache/accumulo/core/iteratorsImpl/IteratorConfigUtil.java
@@ -24,6 +24,7 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -75,28 +76,48 @@ public class IteratorConfigUtil {
   }
 
   /**
-   * Generate the initial (default) properties for a table
+   * Get the initial (default) properties for a table. This includes
+   * {@link #getInitialTableIterators()} and a constraint {@link 
DefaultKeySizeConstraint}
    *
-   * @param limitVersion include a VersioningIterator at priority 20 that 
retains a single version
-   *        of a given K/V pair.
-   * @return A map of Table properties
+   * @return A map of default Table properties
    */
-  public static Map<String,String> generateInitialTableProperties(boolean 
limitVersion) {
+  public static Map<String,String> getInitialTableProperties() {
+    TreeMap<String,String> props = new TreeMap<>(getInitialTableIterators());
+
+    props.put(Property.TABLE_CONSTRAINT_PREFIX + "1", 
DefaultKeySizeConstraint.class.getName());
+
+    return props;
+  }
+
+  /**
+   * For all iterator scopes, includes a {@link VersioningIterator} at 
priority 20 that retains a
+   * single version of a given K/V pair.
+   *
+   * @return a map of default Table iterator properties
+   * @see #getInitialTableIteratorSettings
+   */
+  public static Map<String,String> getInitialTableIterators() {
     TreeMap<String,String> props = new TreeMap<>();
 
-    if (limitVersion) {
-      for (IteratorScope iterScope : IteratorScope.values()) {
-        props.put(Property.TABLE_ITERATOR_PREFIX + iterScope.name() + ".vers",
-            "20," + VersioningIterator.class.getName());
-        props.put(Property.TABLE_ITERATOR_PREFIX + iterScope.name() + 
".vers.opt.maxVersions", "1");
-      }
+    for (IteratorScope iterScope : IteratorScope.values()) {
+      props.put(Property.TABLE_ITERATOR_PREFIX + iterScope.name() + ".vers",
+          "20," + VersioningIterator.class.getName());
+      props.put(Property.TABLE_ITERATOR_PREFIX + iterScope.name() + 
".vers.opt.maxVersions", "1");
     }
 
-    props.put(Property.TABLE_CONSTRAINT_PREFIX + "1", 
DefaultKeySizeConstraint.class.getName());
-
     return props;
   }
 
+  /**
+   *
+   * @return a map of the default Table iterator settings
+   * @see #getInitialTableIterators
+   */
+  public static Map<IteratorSetting,EnumSet<IteratorScope>> 
getInitialTableIteratorSettings() {
+    return Map.of(new IteratorSetting(20, "vers", 
VersioningIterator.class.getName(),
+        Map.of("maxVersions", "1")), EnumSet.allOf(IteratorScope.class));
+  }
+
   public static List<IterInfo> parseIterConf(IteratorScope scope, 
List<IterInfo> iters,
       Map<String,Map<String,String>> allOptions, AccumuloConfiguration conf) {
     Map<String,String> properties = 
conf.getAllPropertiesWithPrefix(getProperty(scope));
diff --git 
a/shell/src/main/java/org/apache/accumulo/shell/commands/CreateTableCommand.java
 
b/shell/src/main/java/org/apache/accumulo/shell/commands/CreateTableCommand.java
index a500e2d607..c730f502a6 100644
--- 
a/shell/src/main/java/org/apache/accumulo/shell/commands/CreateTableCommand.java
+++ 
b/shell/src/main/java/org/apache/accumulo/shell/commands/CreateTableCommand.java
@@ -62,7 +62,9 @@ public class CreateTableCommand extends Command {
   private Option createTableOptSplit;
   private Option createTableOptTimeLogical;
   private Option createTableOptTimeMillis;
+  @Deprecated(since = "2.1.4")
   private Option createTableNoDefaultIters;
+  private Option createTableNoDefaultTableProps;
   private Option createTableOptEVC;
   private Option base64Opt;
   private Option createTableOptFormatter;
@@ -150,29 +152,38 @@ public class CreateTableCommand extends Command {
     }
 
     // Copy configuration options if flag was set
+    Map<String,String> srcTableConfig = null;
     if (cl.hasOption(createTableOptCopyConfig.getOpt())) {
       String srcTable = cl.getOptionValue(createTableOptCopyConfig.getOpt());
       if (cl.hasOption(createTableOptExcludeParentProps.getLongOpt())) {
         // copy properties, excludes parent properties in configuration
-        Map<String,String> tableProps =
+        srcTableConfig =
             
shellState.getAccumuloClient().tableOperations().getTableProperties(srcTable);
-        tableProps.entrySet().stream()
-            .filter(entry -> Property.isValidTablePropertyKey(entry.getKey()))
-            .forEach(entry -> initProperties.put(entry.getKey(), 
entry.getValue()));
       } else {
         // copy configuration (include parent properties)
-        final Map<String,String> configuration =
+        srcTableConfig =
             
shellState.getAccumuloClient().tableOperations().getConfiguration(srcTable);
-        configuration.entrySet().stream()
-            .filter(entry -> Property.isValidTablePropertyKey(entry.getKey()))
-            .forEach(entry -> initProperties.put(entry.getKey(), 
entry.getValue()));
       }
+      srcTableConfig.entrySet().stream()
+          .filter(entry -> Property.isValidTablePropertyKey(entry.getKey()))
+          .forEach(entry -> initProperties.put(entry.getKey(), 
entry.getValue()));
+      // we want to copy the config exactly, specify no defaults so default 
settings copied from
+      // src (above) are all that are added to dest (if any)
+      ntc = ntc.withoutDefaults();
     }
 
     // if no defaults selected, remove, even if copied from configuration or 
properties
-    if (cl.hasOption(createTableNoDefaultIters.getOpt())) {
-      Set<String> initialProps = 
IteratorConfigUtil.generateInitialTableProperties(true).keySet();
+    final boolean noDefaultIters = 
cl.hasOption(createTableNoDefaultIters.getOpt());
+    if (noDefaultIters || 
cl.hasOption(createTableNoDefaultTableProps.getOpt())) {
+      if (noDefaultIters) {
+        Shell.log.warn("{} ({}) option is deprecated and will be removed in a 
future version.",
+            createTableNoDefaultIters.getLongOpt(), 
createTableNoDefaultIters.getOpt());
+      }
+      // handles if default props were copied over
+      Set<String> initialProps = 
IteratorConfigUtil.getInitialTableProperties().keySet();
       initialProps.forEach(initProperties::remove);
+      // prevents default props from being added in create table call
+      ntc = ntc.withoutDefaults();
     }
 
     // Load custom formatter if set
@@ -331,7 +342,9 @@ public class CreateTableCommand extends Command {
     createTableOptTimeLogical = new Option("tl", "time-logical", false, "use 
logical time");
     createTableOptTimeMillis = new Option("tm", "time-millis", false, "use 
time in milliseconds");
     createTableNoDefaultIters = new Option("ndi", "no-default-iterators", 
false,
-        "prevent creation of the normal default iterator set");
+        "deprecated; use --no-default-table-props (-ndtp) instead. Prevent 
creation of the normal default iterator set");
+    createTableNoDefaultTableProps = new Option("ndtp", 
"no-default-table-props", false,
+        "prevent creation of the default iterator and default key size limit");
     createTableOptEVC = new Option("evc", "enable-visibility-constraint", 
false,
         "prevent users from writing data they cannot read. When enabling this,"
             + " consider disabling bulk import and alter table.");
@@ -379,6 +392,7 @@ public class CreateTableCommand extends Command {
     o.addOption(createTableOptCopyConfig);
     o.addOption(createTableOptExcludeParentProps);
     o.addOption(createTableNoDefaultIters);
+    o.addOption(createTableNoDefaultTableProps);
     o.addOption(createTableOptEVC);
     o.addOption(createTableOptFormatter);
     o.addOption(createTableOptInitProp);
diff --git 
a/test/src/main/java/org/apache/accumulo/test/ConditionalWriterIT.java 
b/test/src/main/java/org/apache/accumulo/test/ConditionalWriterIT.java
index 4fb97d84ec..bccbc0ced2 100644
--- a/test/src/main/java/org/apache/accumulo/test/ConditionalWriterIT.java
+++ b/test/src/main/java/org/apache/accumulo/test/ConditionalWriterIT.java
@@ -521,8 +521,7 @@ public class ConditionalWriterIT extends 
SharedMiniClusterBase {
     try (AccumuloClient client = 
Accumulo.newClient().from(getClientProps()).build()) {
       String tableName = getUniqueNames(1)[0];
 
-      client.tableOperations().create(tableName,
-          new NewTableConfiguration().withoutDefaultIterators());
+      client.tableOperations().create(tableName, new 
NewTableConfiguration().withoutDefaults());
 
       try (BatchWriter bw = client.createBatchWriter(tableName)) {
 
diff --git a/test/src/main/java/org/apache/accumulo/test/NamespacesIT.java 
b/test/src/main/java/org/apache/accumulo/test/NamespacesIT.java
index f990ae5927..f8ff450738 100644
--- a/test/src/main/java/org/apache/accumulo/test/NamespacesIT.java
+++ b/test/src/main/java/org/apache/accumulo/test/NamespacesIT.java
@@ -554,7 +554,7 @@ public class NamespacesIT extends SharedMiniClusterBase {
   public void verifyConstraintInheritance() throws Exception {
     String t1 = namespace + ".1";
     c.namespaceOperations().create(namespace);
-    c.tableOperations().create(t1, new 
NewTableConfiguration().withoutDefaultIterators());
+    c.tableOperations().create(t1, new 
NewTableConfiguration().withoutDefaults());
     String constraintClassName = NumericValueConstraint.class.getName();
 
     assertFalse(
diff --git 
a/test/src/main/java/org/apache/accumulo/test/NewTableConfigurationIT.java 
b/test/src/main/java/org/apache/accumulo/test/NewTableConfigurationIT.java
index ae4e1a5685..db9564ff77 100644
--- a/test/src/main/java/org/apache/accumulo/test/NewTableConfigurationIT.java
+++ b/test/src/main/java/org/apache/accumulo/test/NewTableConfigurationIT.java
@@ -20,6 +20,8 @@ package org.apache.accumulo.test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.time.Duration;
 import java.util.Collections;
@@ -39,7 +41,9 @@ import org.apache.accumulo.core.client.TableExistsException;
 import org.apache.accumulo.core.client.TableNotFoundException;
 import org.apache.accumulo.core.client.admin.NewTableConfiguration;
 import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.data.constraints.DefaultKeySizeConstraint;
 import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iteratorsImpl.IteratorConfigUtil;
 import org.apache.accumulo.harness.SharedMiniClusterBase;
 import org.apache.hadoop.io.Text;
 import org.junit.jupiter.api.AfterAll;
@@ -199,7 +203,7 @@ public class NewTableConfigurationIT extends 
SharedMiniClusterBase {
       AccumuloException, TableExistsException, TableNotFoundException {
     try (AccumuloClient client = 
Accumulo.newClient().from(getClientProps()).build()) {
       String tableName = getUniqueNames(2)[0];
-      NewTableConfiguration ntc = new 
NewTableConfiguration().withoutDefaultIterators();
+      NewTableConfiguration ntc = new 
NewTableConfiguration().withoutDefaults();
 
       Map<String,Set<Text>> lgroups = new HashMap<>();
       lgroups.put("lg1", Set.of(new Text("colF")));
@@ -336,7 +340,7 @@ public class NewTableConfigurationIT extends 
SharedMiniClusterBase {
     try (AccumuloClient client = 
Accumulo.newClient().from(getClientProps()).build()) {
       String tableName = getUniqueNames(2)[0];
 
-      NewTableConfiguration ntc = new 
NewTableConfiguration().withoutDefaultIterators();
+      NewTableConfiguration ntc = new 
NewTableConfiguration().withoutDefaults();
       IteratorSetting setting = new IteratorSetting(10, "myIterator", 
"my.class");
       ntc.attachIterator(setting);
       client.tableOperations().create(tableName, ntc);
@@ -482,7 +486,7 @@ public class NewTableConfigurationIT extends 
SharedMiniClusterBase {
       Map<String,Set<Text>> lgroups = new HashMap<>();
       lgroups.put("lgp", Set.of(new Text("col")));
 
-      NewTableConfiguration ntc = new 
NewTableConfiguration().withoutDefaultIterators()
+      NewTableConfiguration ntc = new NewTableConfiguration().withoutDefaults()
           .attachIterator(setting, 
EnumSet.of(IteratorScope.scan)).setLocalityGroups(lgroups);
 
       client.tableOperations().create(tableName, ntc);
@@ -517,6 +521,79 @@ public class NewTableConfigurationIT extends 
SharedMiniClusterBase {
     }
   }
 
+  @Test
+  public void testConflictsWithDefaults() throws Exception {
+    // tests trying to add properties that conflict with the default table 
properties
+    String[] tableNames = getUniqueNames(3);
+    String table = tableNames[0];
+    String table2 = tableNames[1];
+    String table3 = tableNames[2];
+    try (AccumuloClient client = 
Accumulo.newClient().from(getClientProps()).build()) {
+      /*
+       * conflicts from iterators set via attachIterator()
+       */
+      // add an iterator with same priority as the default iterator
+      var iterator1 = new IteratorSetting(20, "foo", "foo.bar");
+      var exception = assertThrows(IllegalStateException.class, () -> 
client.tableOperations()
+          .create(table, new 
NewTableConfiguration().attachIterator(iterator1)));
+      assertTrue(exception.getMessage().contains("iterator priority 
conflict"));
+      // add an iterator with same name as the default iterator
+      var iterator2 = new IteratorSetting(10, "vers", "foo.bar");
+      exception = assertThrows(IllegalStateException.class, () -> 
client.tableOperations()
+          .create(table, new 
NewTableConfiguration().attachIterator(iterator2)));
+      assertTrue(exception.getMessage().contains("iterator name conflict"));
+      // try to attach the exact default iterators, should not present a 
conflict as they are
+      // equivalent to what would be added
+      IteratorConfigUtil.getInitialTableIteratorSettings().forEach((setting, 
scopes) -> {
+        try {
+          client.tableOperations().create(table,
+              new NewTableConfiguration().attachIterator(setting, scopes));
+        } catch (Exception e) {
+          throw new RuntimeException(e);
+        }
+      });
+
+      /*
+       * conflicts from iterators set via properties
+       */
+      // add an iterator with same priority as the default iterator
+      Map<String,String> props = new HashMap<>();
+      for (IteratorScope iterScope : IteratorScope.values()) {
+        props.put(Property.TABLE_ITERATOR_PREFIX + iterScope.name() + ".foo", 
"20,foo.bar");
+      }
+      exception = assertThrows(IllegalStateException.class, () -> 
client.tableOperations()
+          .create(table2, new NewTableConfiguration().setProperties(props)));
+      assertTrue(exception.getMessage().contains("iterator priority 
conflict"));
+      props.clear();
+      // add an iterator with same name as the default iterator
+      for (IteratorScope iterScope : IteratorScope.values()) {
+        props.put(Property.TABLE_ITERATOR_PREFIX + iterScope.name() + ".vers", 
"10,foo.bar");
+      }
+      exception = assertThrows(IllegalStateException.class, () -> 
client.tableOperations()
+          .create(table2, new NewTableConfiguration().setProperties(props)));
+      assertTrue(exception.getMessage().contains("iterator name conflict"));
+      props.clear();
+      // try to attach the exact default iterators, should not present a 
conflict as they are
+      // equivalent to what would be added
+      client.tableOperations().create(table2,
+          new 
NewTableConfiguration().setProperties(IteratorConfigUtil.getInitialTableIterators()));
+
+      /*
+       * conflicts with default, non-iterator properties
+       */
+      // setting a value different from default should throw
+      props.put(Property.TABLE_CONSTRAINT_PREFIX + "1", "foo");
+      exception = assertThrows(IllegalStateException.class, () -> 
client.tableOperations()
+          .create(table3, new NewTableConfiguration().setProperties(props)));
+      assertTrue(exception.getMessage()
+          .contains("conflict for property " + 
Property.TABLE_CONSTRAINT_PREFIX + "1"));
+      props.clear();
+      // setting a value equal to default should be fine
+      props.put(Property.TABLE_CONSTRAINT_PREFIX + "1", 
DefaultKeySizeConstraint.class.getName());
+      client.tableOperations().create(table3, new 
NewTableConfiguration().setProperties(props));
+    }
+  }
+
   /**
    * Verify the expected iterator properties exist.
    */
diff --git a/test/src/main/java/org/apache/accumulo/test/TableOperationsIT.java 
b/test/src/main/java/org/apache/accumulo/test/TableOperationsIT.java
index 2ca5565eb2..b49c3eb64a 100644
--- a/test/src/main/java/org/apache/accumulo/test/TableOperationsIT.java
+++ b/test/src/main/java/org/apache/accumulo/test/TableOperationsIT.java
@@ -62,11 +62,13 @@ import org.apache.accumulo.core.data.Mutation;
 import org.apache.accumulo.core.data.PartialKey;
 import org.apache.accumulo.core.data.Value;
 import org.apache.accumulo.core.data.constraints.DefaultKeySizeConstraint;
+import org.apache.accumulo.core.iterators.IteratorUtil;
 import org.apache.accumulo.core.security.Authorizations;
 import org.apache.accumulo.core.security.TablePermission;
 import org.apache.accumulo.harness.AccumuloClusterHarness;
 import org.apache.accumulo.test.functional.BadIterator;
 import org.apache.accumulo.test.functional.FunctionalTestUtils;
+import org.apache.accumulo.test.util.Wait;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.hadoop.io.Text;
 import org.junit.jupiter.api.AfterEach;
@@ -454,4 +456,64 @@ public class TableOperationsIT extends 
AccumuloClusterHarness {
         "specified table does not exist");
   }
 
+  @Test
+  public void testTablePropsEqual() throws Exception {
+    // Want to ensure users can somehow create a table via the Java API that 
has exactly
+    // the same properties of another table (including changes to default 
properties), similar to
+    // the copy config option of the create table Shell command.
+
+    // default iterator property which is automatically set on creation of all 
tables; want to
+    // ensure removal and changing this type of prop is retained on copying to 
another table
+    final String defaultScanIterProp = Property.TABLE_ITERATOR_PREFIX
+        + IteratorUtil.IteratorScope.scan.name().toLowerCase() + ".vers";
+    final String defaultScanIterPropMaxVers = Property.TABLE_ITERATOR_PREFIX
+        + IteratorUtil.IteratorScope.scan.name().toLowerCase() + 
".vers.opt.maxVersions";
+    final String defaultScanIterPropMaxVersDefault = "1";
+    final String defaultScanIterPropMaxVersNew = "999";
+    final String customTableProp = 
Property.TABLE_ARBITRARY_PROP_PREFIX.getKey() + "foo";
+    final String customTablePropVal = "bar";
+    final String[] names = getUniqueNames(3);
+    final String table1 = names[0];
+    final String table2 = names[1];
+    final String table3 = names[2];
+
+    try (AccumuloClient client = 
Accumulo.newClient().from(getClientProps()).build()) {
+      client.tableOperations().create(table1,
+          new NewTableConfiguration().setProperties(Map.of(customTableProp, 
customTablePropVal)));
+
+      Wait.waitFor(() -> {
+        var table1Props = client.tableOperations().getTableProperties(table1);
+        return table1Props.get(customTableProp).equals(customTablePropVal)
+            && table1Props.get(defaultScanIterProp) != null && table1Props
+                
.get(defaultScanIterPropMaxVers).equals(defaultScanIterPropMaxVersDefault);
+      });
+
+      // testing both modifying and deleting a default prop
+      client.tableOperations().modifyProperties(table1, props -> {
+        props.replace(defaultScanIterPropMaxVers, 
defaultScanIterPropMaxVersNew);
+        props.remove(defaultScanIterProp);
+      });
+
+      Wait.waitFor(() -> {
+        var table1Props = client.tableOperations().getTableProperties(table1);
+        return table1Props.get(customTableProp).equals(customTablePropVal)
+            && table1Props.get(defaultScanIterProp) == null
+            && 
table1Props.get(defaultScanIterPropMaxVers).equals(defaultScanIterPropMaxVersNew);
+      });
+
+      // one option to create a table with the same config, including default 
prop changes
+      client.tableOperations().create(table2, new 
NewTableConfiguration().withoutDefaults()
+          .setProperties(client.tableOperations().getTableProperties(table1)));
+      // another option is cloning the table
+      client.tableOperations().clone(table1, table3, 
CloneConfiguration.empty());
+
+      Wait.waitFor(() -> {
+        var table1Props = client.tableOperations().getTableProperties(table1);
+        var table2Props = client.tableOperations().getTableProperties(table2);
+        var table3Props = client.tableOperations().getTableProperties(table3);
+        return table1Props.equals(table2Props) && 
table1Props.equals(table3Props);
+      });
+    }
+  }
+
 }
diff --git 
a/test/src/main/java/org/apache/accumulo/test/replication/CyclicReplicationIT.java
 
b/test/src/main/java/org/apache/accumulo/test/replication/CyclicReplicationIT.java
index 2db8808e6c..11cf52e963 100644
--- 
a/test/src/main/java/org/apache/accumulo/test/replication/CyclicReplicationIT.java
+++ 
b/test/src/main/java/org/apache/accumulo/test/replication/CyclicReplicationIT.java
@@ -263,12 +263,12 @@ public class CyclicReplicationIT extends AccumuloITBase {
                   manager1Cluster.getZooKeepers())));
 
       clientManager1.tableOperations().create(manager1Table,
-          new NewTableConfiguration().withoutDefaultIterators());
+          new NewTableConfiguration().withoutDefaults());
       String manager1TableId = 
clientManager1.tableOperations().tableIdMap().get(manager1Table);
       assertNotNull(manager1TableId);
 
       clientManager2.tableOperations().create(manager2Table,
-          new NewTableConfiguration().withoutDefaultIterators());
+          new NewTableConfiguration().withoutDefaults());
       String manager2TableId = 
clientManager2.tableOperations().tableIdMap().get(manager2Table);
       assertNotNull(manager2TableId);
 
diff --git 
a/test/src/main/java/org/apache/accumulo/test/shell/ShellCreateTableIT.java 
b/test/src/main/java/org/apache/accumulo/test/shell/ShellCreateTableIT.java
index 18309fe5d8..a723d50dea 100644
--- a/test/src/main/java/org/apache/accumulo/test/shell/ShellCreateTableIT.java
+++ b/test/src/main/java/org/apache/accumulo/test/shell/ShellCreateTableIT.java
@@ -50,7 +50,9 @@ import org.apache.accumulo.core.client.AccumuloClient;
 import org.apache.accumulo.core.client.AccumuloException;
 import org.apache.accumulo.core.client.AccumuloSecurityException;
 import org.apache.accumulo.core.client.TableNotFoundException;
+import org.apache.accumulo.core.conf.Property;
 import org.apache.accumulo.core.data.TableId;
+import org.apache.accumulo.core.iterators.IteratorUtil;
 import org.apache.accumulo.core.util.TextUtil;
 import org.apache.accumulo.harness.MiniClusterConfigurationCallback;
 import org.apache.accumulo.harness.SharedMiniClusterBase;
@@ -872,6 +874,78 @@ public class ShellCreateTableIT extends 
SharedMiniClusterBase {
     ts.exec("createtable --copy-config " + names[0] + " --exclude-parent " + 
names[2], true);
   }
 
+  @Test
+  public void testCreateTableCopiedConfig() throws Exception {
+    // tests that changes to default iterator settings on table1 are carried 
over to a new table2
+    // when created using the config of table1
+    final String[] tableNames = getUniqueNames(2);
+    final String table1 = tableNames[0];
+    final String table2 = tableNames[1];
+
+    ts.exec("createtable " + table1, true);
+    for (IteratorUtil.IteratorScope iterScope : 
IteratorUtil.IteratorScope.values()) {
+      ts.exec("config -t " + table1 + " -d " + Property.TABLE_ITERATOR_PREFIX 
+ iterScope.name()
+          + ".vers", true);
+      ts.exec("config -t " + table1 + " -s " + Property.TABLE_ITERATOR_PREFIX 
+ iterScope.name()
+          + ".vers.opt.maxVersions=999", true);
+    }
+
+    ts.exec("createtable " + table2 + " -cc " + table1, true);
+    for (IteratorUtil.IteratorScope iterScope : 
IteratorUtil.IteratorScope.values()) {
+      var res = ts.exec(
+          "config -t " + table2 + " -f " + Property.TABLE_ITERATOR_PREFIX + 
iterScope.name(), true);
+      // verify deleted table1 prop is also deleted in table2
+      // note the space: ".vers "
+      assertFalse(res.contains(Property.TABLE_ITERATOR_PREFIX + 
iterScope.name() + ".vers "));
+      // verify changed table1 prop is also changed in table2
+      assertTrue(res
+          .contains(Property.TABLE_ITERATOR_PREFIX + iterScope.name() + 
".vers.opt.maxVersions"));
+      assertTrue(res.contains("999"));
+    }
+  }
+
+  @Test
+  public void testCreateTableNoDefaultIterators1() throws Exception {
+    // tests the no default iterators setting
+    final String table1 = getUniqueNames(1)[0];
+
+    ts.exec("createtable -ndi " + table1, true);
+    // verify no default iterator props
+    for (IteratorUtil.IteratorScope iterScope : 
IteratorUtil.IteratorScope.values()) {
+      var res = ts.exec(
+          "config -t " + table1 + " -f " + Property.TABLE_ITERATOR_PREFIX + 
iterScope.name(), true);
+      assertFalse(res.contains(Property.TABLE_ITERATOR_PREFIX + 
iterScope.name() + ".vers"));
+    }
+  }
+
+  @Test
+  public void testCreateTableNoDefaultIterators2() throws Exception {
+    // tests the no default iterators setting
+    final String[] tableNames = getUniqueNames(3);
+    final String table1 = tableNames[0];
+    final String table2 = tableNames[1];
+    final String table3 = tableNames[2];
+
+    // create table1 with default iterators, create table2 without default 
iterators but copying
+    // table1 config... Expect no default iterators
+    ts.exec("createtable " + table1, true);
+    // make sure order doesn't matter
+    ts.exec("createtable " + table2 + " -ndi -cc " + table1);
+    ts.exec("createtable " + table3 + " -cc " + table1 + " -ndi");
+    for (IteratorUtil.IteratorScope iterScope : 
IteratorUtil.IteratorScope.values()) {
+      var res = ts.exec(
+          "config -t " + table1 + " -f " + Property.TABLE_ITERATOR_PREFIX + 
iterScope.name(), true);
+      // verify default iterator props for table1 but not table2 or table3
+      assertTrue(res.contains(Property.TABLE_ITERATOR_PREFIX + 
iterScope.name() + ".vers"));
+      res = ts.exec(
+          "config -t " + table2 + " -f " + Property.TABLE_ITERATOR_PREFIX + 
iterScope.name(), true);
+      assertFalse(res.contains(Property.TABLE_ITERATOR_PREFIX + 
iterScope.name() + ".vers"));
+      res = ts.exec(
+          "config -t " + table3 + " -f " + Property.TABLE_ITERATOR_PREFIX + 
iterScope.name(), true);
+      assertFalse(res.contains(Property.TABLE_ITERATOR_PREFIX + 
iterScope.name() + ".vers"));
+    }
+  }
+
   private Collection<Text> generateNonBinarySplits(final int numItems, final 
int len) {
     Set<Text> splits = new HashSet<>();
     for (int i = 0; i < numItems; i++) {


Reply via email to