Search in sources :

Example 41 with ColumnIdentifier

use of org.apache.cassandra.cql3.ColumnIdentifier in project cassandra by apache.

the class SSTableGenerator method delete.

Mutation delete(long lts, long pd, Query query) {
    Object[] partitionKey = schema.inflatePartitionKey(pd);
    WhereClause.Builder builder = new WhereClause.Builder();
    List<ColumnIdentifier> variableNames = new ArrayList<>();
    List<ByteBuffer> values = new ArrayList<>();
    for (int i = 0; i < partitionKey.length; i++) {
        String name = schema.partitionKeys.get(i).name;
        ColumnMetadata columnDef = metadata.getColumn(ByteBufferUtil.bytes(name));
        variableNames.add(columnDef.name);
        values.add(ByteBufferUtil.objectToBytes(partitionKey[i]));
        builder.add(new SingleColumnRelation(ColumnIdentifier.getInterned(name, true), toOperator(Relation.RelationKind.EQ), new AbstractMarker.Raw(values.size() - 1)));
    }
    for (Relation relation : query.relations) {
        String name = relation.column();
        ColumnMetadata columnDef = metadata.getColumn(ByteBufferUtil.bytes(relation.column()));
        variableNames.add(columnDef.name);
        values.add(ByteBufferUtil.objectToBytes(relation.value()));
        builder.add(new SingleColumnRelation(ColumnIdentifier.getInterned(name, false), toOperator(relation.kind), new AbstractMarker.Raw(values.size() - 1)));
    }
    StatementRestrictions restrictions = new StatementRestrictions(StatementType.DELETE, metadata, builder.build(), new VariableSpecifications(variableNames), false, false, false, false);
    QueryOptions options = QueryOptions.forInternalCalls(ConsistencyLevel.QUORUM, values);
    SortedSet<ClusteringBound<?>> startBounds = restrictions.getClusteringColumnsBounds(Bound.START, options);
    SortedSet<ClusteringBound<?>> endBounds = restrictions.getClusteringColumnsBounds(Bound.END, options);
    Slices slices = DeleteStatement.toSlices(metadata, startBounds, endBounds);
    assert slices.size() == 1;
    int deletionTime = FBUtilities.nowInSeconds();
    long rts = clock.rts(lts);
    return new RowUpdateBuilder(metadata, deletionTime, rts, metadata.params.defaultTimeToLive, serializePartitionKey(store, partitionKey)).noRowMarker().addRangeTombstone(new RangeTombstone(slices.get(0), new DeletionTime(rts, deletionTime))).build();
}
Also used : ColumnMetadata(org.apache.cassandra.schema.ColumnMetadata) Slices(org.apache.cassandra.db.Slices) RowUpdateBuilder(org.apache.cassandra.db.RowUpdateBuilder) WhereClause(org.apache.cassandra.cql3.WhereClause) ArrayList(java.util.ArrayList) QueryOptions(org.apache.cassandra.cql3.QueryOptions) SingleColumnRelation(org.apache.cassandra.cql3.SingleColumnRelation) Relation(harry.operations.Relation) RowUpdateBuilder(org.apache.cassandra.db.RowUpdateBuilder) StatementRestrictions(org.apache.cassandra.cql3.restrictions.StatementRestrictions) RangeTombstone(org.apache.cassandra.db.RangeTombstone) DeletionTime(org.apache.cassandra.db.DeletionTime) ByteBuffer(java.nio.ByteBuffer) VariableSpecifications(org.apache.cassandra.cql3.VariableSpecifications) SingleColumnRelation(org.apache.cassandra.cql3.SingleColumnRelation) ClusteringBound(org.apache.cassandra.db.ClusteringBound) ColumnIdentifier(org.apache.cassandra.cql3.ColumnIdentifier)

Example 42 with ColumnIdentifier

use of org.apache.cassandra.cql3.ColumnIdentifier in project cassandra by apache.

the class CreateViewStatement method apply.

public Keyspaces apply(Keyspaces schema) {
    if (!DatabaseDescriptor.getMaterializedViewsEnabled())
        throw ire("Materialized views are disabled. Enable in cassandra.yaml to use.");
    /*
         * Basic dependency validations
         */
    KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
    if (null == keyspace)
        throw ire("Keyspace '%s' doesn't exist", keyspaceName);
    if (keyspace.createReplicationStrategy().hasTransientReplicas())
        throw new InvalidRequestException("Materialized views are not supported on transiently replicated keyspaces");
    TableMetadata table = keyspace.tables.getNullable(tableName);
    if (null == table)
        throw ire("Base table '%s' doesn't exist", tableName);
    if (keyspace.hasTable(viewName))
        throw ire("Cannot create materialized view '%s' - a table with the same name already exists", viewName);
    if (keyspace.hasView(viewName)) {
        if (ifNotExists)
            return schema;
        throw new AlreadyExistsException(keyspaceName, viewName);
    }
    if (table.isCounter())
        throw ire("Materialized views are not supported on counter tables");
    if (table.isView())
        throw ire("Materialized views cannot be created against other materialized views");
    // Guardrails on table properties
    Guardrails.tableProperties.guard(attrs.updatedProperties(), attrs::removeProperty, state);
    // Guardrail to limit number of mvs per table
    Iterable<ViewMetadata> tableViews = keyspace.views.forTable(table.id);
    Guardrails.materializedViewsPerTable.guard(Iterables.size(tableViews) + 1, String.format("%s on table %s", viewName, table.name), state);
    if (table.params.gcGraceSeconds == 0) {
        throw ire("Cannot create materialized view '%s' for base table " + "'%s' with gc_grace_seconds of 0, since this value is " + "used to TTL undelivered updates. Setting gc_grace_seconds" + " too low might cause undelivered updates to expire " + "before being replayed.", viewName, tableName);
    }
    /*
         * Process SELECT clause
         */
    Set<ColumnIdentifier> selectedColumns = new HashSet<>();
    if (// SELECT *
    rawColumns.isEmpty())
        table.columns().forEach(c -> selectedColumns.add(c.name));
    rawColumns.forEach(selector -> {
        if (null != selector.alias)
            throw ire("Cannot use aliases when defining a materialized view (got %s)", selector);
        if (!(selector.selectable instanceof Selectable.RawIdentifier))
            throw ire("Can only select columns by name when defining a materialized view (got %s)", selector.selectable);
        // will throw IRE if the column doesn't exist in the base table
        ColumnMetadata column = (ColumnMetadata) selector.selectable.prepare(table);
        selectedColumns.add(column.name);
    });
    selectedColumns.stream().map(table::getColumn).filter(ColumnMetadata::isStatic).findAny().ifPresent(c -> {
        throw ire("Cannot include static column '%s' in materialized view '%s'", c, viewName);
    });
    if (partitionKeyColumns.isEmpty())
        throw ire("Must provide at least one partition key column for materialized view '%s'", viewName);
    HashSet<ColumnIdentifier> primaryKeyColumns = new HashSet<>();
    concat(partitionKeyColumns, clusteringColumns).forEach(name -> {
        ColumnMetadata column = table.getColumn(name);
        if (null == column || !selectedColumns.contains(name))
            throw ire("Unknown column '%s' referenced in PRIMARY KEY for materialized view '%s'", name, viewName);
        if (!primaryKeyColumns.add(name))
            throw ire("Duplicate column '%s' in PRIMARY KEY clause for materialized view '%s'", name, viewName);
        AbstractType<?> type = column.type;
        if (type.isMultiCell()) {
            if (type.isCollection())
                throw ire("Invalid non-frozen collection type '%s' for PRIMARY KEY column '%s'", type, name);
            else
                throw ire("Invalid non-frozen user-defined type '%s' for PRIMARY KEY column '%s'", type, name);
        }
        if (type.isCounter())
            throw ire("counter type is not supported for PRIMARY KEY column '%s'", name);
        if (type.referencesDuration())
            throw ire("duration type is not supported for PRIMARY KEY column '%s'", name);
    });
    // If we give a clustering order, we must explicitly do so for all aliases and in the order of the PK
    if (!clusteringOrder.isEmpty() && !clusteringColumns.equals(new ArrayList<>(clusteringOrder.keySet())))
        throw ire("Clustering key columns must exactly match columns in CLUSTERING ORDER BY directive");
    /*
         * We need to include all of the primary key columns from the base table in order to make sure that we do not
         * overwrite values in the view. We cannot support "collapsing" the base table into a smaller number of rows in
         * the view because if we need to generate a tombstone, we have no way of knowing which value is currently being
         * used in the view and whether or not to generate a tombstone. In order to not surprise our users, we require
         * that they include all of the columns. We provide them with a list of all of the columns left to include.
         */
    List<ColumnIdentifier> missingPrimaryKeyColumns = Lists.newArrayList(filter(transform(table.primaryKeyColumns(), c -> c.name), c -> !primaryKeyColumns.contains(c)));
    if (!missingPrimaryKeyColumns.isEmpty()) {
        throw ire("Cannot create materialized view '%s' without primary key columns %s from base table '%s'", viewName, join(", ", transform(missingPrimaryKeyColumns, ColumnIdentifier::toString)), tableName);
    }
    Set<ColumnIdentifier> regularBaseTableColumnsInViewPrimaryKey = new HashSet<>(primaryKeyColumns);
    transform(table.primaryKeyColumns(), c -> c.name).forEach(regularBaseTableColumnsInViewPrimaryKey::remove);
    if (regularBaseTableColumnsInViewPrimaryKey.size() > 1) {
        throw ire("Cannot include more than one non-primary key column in materialized view primary key (got %s)", join(", ", transform(regularBaseTableColumnsInViewPrimaryKey, ColumnIdentifier::toString)));
    }
    /*
         * Process WHERE clause
         */
    if (whereClause.containsTokenRelations())
        throw new InvalidRequestException("Cannot use token relation when defining a materialized view");
    if (whereClause.containsCustomExpressions())
        throw ire("WHERE clause for materialized view '%s' cannot contain custom index expressions", viewName);
    StatementRestrictions restrictions = new StatementRestrictions(StatementType.SELECT, table, whereClause, VariableSpecifications.empty(), false, false, true, true);
    List<ColumnIdentifier> nonRestrictedPrimaryKeyColumns = Lists.newArrayList(filter(primaryKeyColumns, name -> !restrictions.isRestricted(table.getColumn(name))));
    if (!nonRestrictedPrimaryKeyColumns.isEmpty()) {
        throw ire("Primary key columns %s must be restricted with 'IS NOT NULL' or otherwise", join(", ", transform(nonRestrictedPrimaryKeyColumns, ColumnIdentifier::toString)));
    }
    // See CASSANDRA-13798
    Set<ColumnMetadata> restrictedNonPrimaryKeyColumns = restrictions.nonPKRestrictedColumns(false);
    if (!restrictedNonPrimaryKeyColumns.isEmpty() && !Boolean.getBoolean("cassandra.mv.allow_filtering_nonkey_columns_unsafe")) {
        throw ire("Non-primary key columns can only be restricted with 'IS NOT NULL' (got: %s restricted illegally)", join(",", transform(restrictedNonPrimaryKeyColumns, ColumnMetadata::toString)));
    }
    /*
         * Validate WITH params
         */
    attrs.validate();
    if (attrs.hasOption(TableParams.Option.DEFAULT_TIME_TO_LIVE)) {
        throw ire("Cannot set default_time_to_live for a materialized view. " + "Data in a materialized view always expire at the same time than " + "the corresponding data in the parent table.");
    }
    /*
         * Build the thing
         */
    TableMetadata.Builder builder = TableMetadata.builder(keyspaceName, viewName);
    if (attrs.hasProperty(TableAttributes.ID))
        builder.id(attrs.getId());
    builder.params(attrs.asNewTableParams()).kind(TableMetadata.Kind.VIEW);
    partitionKeyColumns.forEach(name -> builder.addPartitionKeyColumn(name, getType(table, name)));
    clusteringColumns.forEach(name -> builder.addClusteringColumn(name, getType(table, name)));
    selectedColumns.stream().filter(name -> !primaryKeyColumns.contains(name)).forEach(name -> builder.addRegularColumn(name, getType(table, name)));
    ViewMetadata view = new ViewMetadata(table.id, table.name, rawColumns.isEmpty(), whereClause, builder.build());
    view.metadata.validate();
    return schema.withAddedOrUpdated(keyspace.withSwapped(keyspace.views.with(view)));
}
Also used : AuditLogContext(org.apache.cassandra.audit.AuditLogContext) Change(org.apache.cassandra.transport.Event.SchemaChange.Change) java.util(java.util) Iterables(com.google.common.collect.Iterables) Iterables.transform(com.google.common.collect.Iterables.transform) Permission(org.apache.cassandra.auth.Permission) AbstractType(org.apache.cassandra.db.marshal.AbstractType) org.apache.cassandra.cql3(org.apache.cassandra.cql3) AlreadyExistsException(org.apache.cassandra.exceptions.AlreadyExistsException) View(org.apache.cassandra.db.view.View) Guardrails(org.apache.cassandra.db.guardrails.Guardrails) Lists(com.google.common.collect.Lists) String.join(java.lang.String.join) Iterables.concat(com.google.common.collect.Iterables.concat) KeyspacesDiff(org.apache.cassandra.schema.Keyspaces.KeyspacesDiff) DatabaseDescriptor(org.apache.cassandra.config.DatabaseDescriptor) InvalidRequestException(org.apache.cassandra.exceptions.InvalidRequestException) ImmutableSet(com.google.common.collect.ImmutableSet) SchemaChange(org.apache.cassandra.transport.Event.SchemaChange) ClientState(org.apache.cassandra.service.ClientState) StatementRestrictions(org.apache.cassandra.cql3.restrictions.StatementRestrictions) AuditLogEntryType(org.apache.cassandra.audit.AuditLogEntryType) StatementType(org.apache.cassandra.cql3.statements.StatementType) Selectable(org.apache.cassandra.cql3.selection.Selectable) ReversedType(org.apache.cassandra.db.marshal.ReversedType) Target(org.apache.cassandra.transport.Event.SchemaChange.Target) RawSelector(org.apache.cassandra.cql3.selection.RawSelector) Iterables.filter(com.google.common.collect.Iterables.filter) org.apache.cassandra.schema(org.apache.cassandra.schema) AlreadyExistsException(org.apache.cassandra.exceptions.AlreadyExistsException) InvalidRequestException(org.apache.cassandra.exceptions.InvalidRequestException) StatementRestrictions(org.apache.cassandra.cql3.restrictions.StatementRestrictions)

Example 43 with ColumnIdentifier

use of org.apache.cassandra.cql3.ColumnIdentifier in project cassandra by apache.

the class SecondaryIndexTest method testUpdatesToMemtableData.

@Test
public void testUpdatesToMemtableData() throws Throwable {
    // verify the contract specified by Index.Indexer::updateRow(oldRowData, newRowData),
    // when a row in the memtable is updated, the indexer should be informed of:
    // * new columns
    // * removed columns
    // * columns whose value, timestamp or ttl have been modified.
    // Any columns which are unchanged by the update are not passed to the Indexer
    // Note that for simplicity this test resets the index between each scenario
    createTable("CREATE TABLE %s (k int, c int, v1 int, v2 int, PRIMARY KEY (k,c))");
    createIndex(format("CREATE CUSTOM INDEX test_index ON %%s() USING '%s'", StubIndex.class.getName()));
    execute("INSERT INTO %s (k, c, v1, v2) VALUES (0, 0, 0, 0) USING TIMESTAMP 0");
    ColumnMetadata v1 = getCurrentColumnFamilyStore().metadata().getColumn(new ColumnIdentifier("v1", true));
    ColumnMetadata v2 = getCurrentColumnFamilyStore().metadata().getColumn(new ColumnIdentifier("v2", true));
    StubIndex index = (StubIndex) getCurrentColumnFamilyStore().indexManager.getIndexByName("test_index");
    assertEquals(1, index.rowsInserted.size());
    // Overwrite a single value, leaving the other untouched
    execute("UPDATE %s USING TIMESTAMP 1 SET v1=1 WHERE k=0 AND c=0");
    assertEquals(1, index.rowsUpdated.size());
    Row oldRow = index.rowsUpdated.get(0).left;
    assertEquals(1, oldRow.columnCount());
    validateCell(oldRow.getCell(v1), v1, ByteBufferUtil.bytes(0), 0);
    Row newRow = index.rowsUpdated.get(0).right;
    assertEquals(1, newRow.columnCount());
    validateCell(newRow.getCell(v1), v1, ByteBufferUtil.bytes(1), 1);
    index.reset();
    // Overwrite both values
    execute("UPDATE %s USING TIMESTAMP 2 SET v1=2, v2=2 WHERE k=0 AND c=0");
    assertEquals(1, index.rowsUpdated.size());
    oldRow = index.rowsUpdated.get(0).left;
    assertEquals(2, oldRow.columnCount());
    validateCell(oldRow.getCell(v1), v1, ByteBufferUtil.bytes(1), 1);
    validateCell(oldRow.getCell(v2), v2, ByteBufferUtil.bytes(0), 0);
    newRow = index.rowsUpdated.get(0).right;
    assertEquals(2, newRow.columnCount());
    validateCell(newRow.getCell(v1), v1, ByteBufferUtil.bytes(2), 2);
    validateCell(newRow.getCell(v2), v2, ByteBufferUtil.bytes(2), 2);
    index.reset();
    // Delete one value
    execute("DELETE v1 FROM %s USING TIMESTAMP 3 WHERE k=0 AND c=0");
    assertEquals(1, index.rowsUpdated.size());
    oldRow = index.rowsUpdated.get(0).left;
    assertEquals(1, oldRow.columnCount());
    validateCell(oldRow.getCell(v1), v1, ByteBufferUtil.bytes(2), 2);
    newRow = index.rowsUpdated.get(0).right;
    assertEquals(1, newRow.columnCount());
    Cell<?> newCell = newRow.getCell(v1);
    assertTrue(newCell.isTombstone());
    assertEquals(3, newCell.timestamp());
    index.reset();
    // Modify the liveness of the primary key, the delta rows should contain
    // no cell data as only the pk was altered, but it should illustrate the
    // change to the liveness info
    execute("INSERT INTO %s(k, c) VALUES (0, 0) USING TIMESTAMP 4");
    assertEquals(1, index.rowsUpdated.size());
    oldRow = index.rowsUpdated.get(0).left;
    assertEquals(0, oldRow.columnCount());
    assertEquals(0, oldRow.primaryKeyLivenessInfo().timestamp());
    newRow = index.rowsUpdated.get(0).right;
    assertEquals(0, newRow.columnCount());
    assertEquals(4, newRow.primaryKeyLivenessInfo().timestamp());
}
Also used : StubIndex(org.apache.cassandra.index.StubIndex) ColumnMetadata(org.apache.cassandra.schema.ColumnMetadata) ColumnIdentifier(org.apache.cassandra.cql3.ColumnIdentifier) Row(org.apache.cassandra.db.rows.Row) Test(org.junit.Test)

Example 44 with ColumnIdentifier

use of org.apache.cassandra.cql3.ColumnIdentifier in project cassandra by apache.

the class CassandraGenerators method createColumnDefinition.

private static ColumnMetadata createColumnDefinition(String ks, String table, ColumnMetadata.Kind kind, Set<String> createdColumnNames, /* This is mutated to check for collisions, so has a side effect outside of normal random generation */
RandomnessSource rnd) {
    Gen<AbstractType<?>> typeGen = AbstractTypeGenerators.typeGen();
    switch(kind) {
        // empty type is also not supported, so filter out
        case PARTITION_KEY:
        case CLUSTERING:
            typeGen = Generators.filter(typeGen, t -> t != EmptyType.instance).map(AbstractType::freeze);
            break;
    }
    if (kind == ColumnMetadata.Kind.CLUSTERING) {
        // when working on a clustering column, add in reversed types periodically
        typeGen = allowReversed(typeGen);
    }
    // filter for unique names
    String str;
    while (!createdColumnNames.add(str = IDENTIFIER_GEN.generate(rnd))) {
    }
    ColumnIdentifier name = new ColumnIdentifier(str, true);
    int position = !kind.isPrimaryKeyKind() ? -1 : (int) rnd.next(Constraint.between(0, 30));
    return new ColumnMetadata(ks, table, name, typeGen.generate(rnd), position, kind);
}
Also used : ColumnMetadata(org.apache.cassandra.schema.ColumnMetadata) AbstractType(org.apache.cassandra.db.marshal.AbstractType) ColumnIdentifier(org.apache.cassandra.cql3.ColumnIdentifier) Constraint(org.quicktheories.impl.Constraint)

Example 45 with ColumnIdentifier

use of org.apache.cassandra.cql3.ColumnIdentifier in project cassandra by apache.

the class SSTableHeaderFixTest method complexTypeDroppedColumnsMatchTest.

@Test
public void complexTypeDroppedColumnsMatchTest() throws Exception {
    File dir = temporaryFolder;
    TableMetadata.Builder cols = TableMetadata.builder("ks", "cf").addPartitionKeyColumn("pk", udtPK).addClusteringColumn("ck", udtCK);
    commonColumns(cols);
    cols.addRegularColumn("tuple_in_tuple", tupleInTuple).addRegularColumn("udt_nested", udtNested).addRegularColumn("udt_in_tuple", udtInTuple).addRegularColumn("tuple_in_composite", tupleInComposite).addRegularColumn("udt_in_composite", udtInComposite).addRegularColumn("udt_in_list", udtInList).addRegularColumn("udt_in_set", udtInSet).addRegularColumn("udt_in_map", udtInMap).addRegularColumn("udt_in_frozen_list", udtInFrozenList).addRegularColumn("udt_in_frozen_set", udtInFrozenSet).addRegularColumn("udt_in_frozen_map", udtInFrozenMap);
    File sstable = buildFakeSSTable(dir, 1, cols, true);
    cols = tableMetadata.unbuild();
    for (String col : new String[] { "tuple_in_tuple", "udt_nested", "udt_in_tuple", "tuple_in_composite", "udt_in_composite", "udt_in_list", "udt_in_set", "udt_in_map", "udt_in_frozen_list", "udt_in_frozen_set", "udt_in_frozen_map" }) {
        ColumnIdentifier ci = new ColumnIdentifier(col, true);
        ColumnMetadata cd = getColDef(col);
        AbstractType<?> dropType = cd.type.expandUserTypes();
        cols.removeRegularOrStaticColumn(ci).recordColumnDrop(new ColumnMetadata(cd.ksName, cd.cfName, cd.name, dropType, cd.position(), cd.kind), FBUtilities.timestampMicros());
    }
    tableMetadata = cols.build();
    SerializationHeader.Component header = readHeader(sstable);
    assertFrozenUdt(header, false, true);
    SSTableHeaderFix headerFix = builder().withPath(sstable.toPath()).build();
    headerFix.execute();
    assertFalse(headerFix.hasError());
    assertTrue(headerFix.hasChanges());
    assertEquals(Sets.newHashSet("pk", "ck", "regular_b", "static_b", "udt_nested"), updatedColumns);
    // must not have re-written the stats-component
    header = readHeader(sstable);
    // do not check the inner types, as the inner types were not fixed in the serialization-header (test thing)
    assertFrozenUdt(header, true, false);
}
Also used : TableMetadata(org.apache.cassandra.schema.TableMetadata) ColumnMetadata(org.apache.cassandra.schema.ColumnMetadata) SerializationHeader(org.apache.cassandra.db.SerializationHeader) ColumnIdentifier(org.apache.cassandra.cql3.ColumnIdentifier) File(org.apache.cassandra.io.util.File) Test(org.junit.Test)

Aggregations

ColumnIdentifier (org.apache.cassandra.cql3.ColumnIdentifier)43 ColumnMetadata (org.apache.cassandra.schema.ColumnMetadata)26 Test (org.junit.Test)15 TableMetadata (org.apache.cassandra.schema.TableMetadata)14 Row (org.apache.cassandra.db.rows.Row)10 ByteBuffer (java.nio.ByteBuffer)9 IndexTarget (org.apache.cassandra.cql3.statements.schema.IndexTarget)8 java.util (java.util)4 ArrayList (java.util.ArrayList)4 DatabaseDescriptor (org.apache.cassandra.config.DatabaseDescriptor)4 PartitionIterator (org.apache.cassandra.db.partitions.PartitionIterator)4 PartitionUpdate (org.apache.cassandra.db.partitions.PartitionUpdate)4 RowIterator (org.apache.cassandra.db.rows.RowIterator)4 ClientState (org.apache.cassandra.service.ClientState)4 Iterables (com.google.common.collect.Iterables)3 StatementRestrictions (org.apache.cassandra.cql3.restrictions.StatementRestrictions)3 UnfilteredRowIterator (org.apache.cassandra.db.rows.UnfilteredRowIterator)3 ImmutableSet (com.google.common.collect.ImmutableSet)2 Iterables.transform (com.google.common.collect.Iterables.transform)2 Lists (com.google.common.collect.Lists)2