예제 #1
0
    /// <summary>
    /// Counts the partitions touched by a scan with optional primary key bounds.
    /// The table is assumed to have three INT8 columns as the primary key.
    /// </summary>
    /// <param name="expectedTablets">The expected number of tablets to satisfy the scan.</param>
    /// <param name="table">The table to scan.</param>
    /// <param name="partitions">The partitions of the table.</param>
    /// <param name="lowerBoundPrimaryKey">The optional lower bound primary key.</param>
    /// <param name="upperBoundPrimaryKey">The optional upper bound primary key.</param>
    private async Task CheckPartitionsPrimaryKeyAsync(
        int expectedTablets,
        KuduTable table,
        List <Partition> partitions,
        sbyte[] lowerBoundPrimaryKey,
        sbyte[] upperBoundPrimaryKey)
    {
        var scanBuilder = _client.NewScanTokenBuilder(table);

        if (lowerBoundPrimaryKey != null)
        {
            var lower = new PartialRow(table.Schema);
            for (int i = 0; i < 3; i++)
            {
                lower.SetSByte(i, lowerBoundPrimaryKey[i]);
            }
            scanBuilder.LowerBound(lower);
        }

        if (upperBoundPrimaryKey != null)
        {
            var upper = new PartialRow(table.Schema);
            for (int i = 0; i < 3; i++)
            {
                upper.SetSByte(i, upperBoundPrimaryKey[i]);
            }
            scanBuilder.ExclusiveUpperBound(upper);
        }

        var pruner = PartitionPruner.Create(scanBuilder);

        int scannedPartitions = 0;

        foreach (var partition in partitions)
        {
            if (!pruner.ShouldPruneForTests(partition))
            {
                scannedPartitions++;
            }
        }

        // Check that the number of ScanTokens built for the scan matches.
        var tokens = await scanBuilder.BuildAsync();

        Assert.Equal(expectedTablets, scannedPartitions);
        Assert.Equal(scannedPartitions, tokens.Count);
        Assert.Equal(expectedTablets == 0 ? 0 : 1, pruner.NumRangesRemaining);
    }
예제 #2
0
    public async ValueTask <List <KuduScanToken> > BuildAsync(
        CancellationToken cancellationToken = default)
    {
        if (LowerBoundPartitionKey.Length != 0 ||
            UpperBoundPartitionKey.Length != 0)
        {
            throw new ArgumentException(
                      "Partition key bounds may not be set on KuduScanTokenBuilder");
        }

        // If the scan is short-circuitable, then return no tokens.
        foreach (var predicate in Predicates.Values)
        {
            if (predicate.Type == PredicateType.None)
            {
                return(new List <KuduScanToken>());
            }
        }

        var proto = new ScanTokenPB();

        if (_includeTableMetadata)
        {
            // Set the table metadata so that a call to the master is not needed when
            // deserializing the token into a scanner.
            var tableMetadataPb = new TableMetadataPB
            {
                TableId         = Table.TableId,
                TableName       = Table.TableName,
                NumReplicas     = Table.NumReplicas,
                Schema          = Table.SchemaPb.Schema,
                PartitionSchema = Table.SchemaPb.PartitionSchema
            };

            if (Table.Owner is not null)
            {
                tableMetadataPb.Owner = Table.Owner;
            }

            if (Table.Comment is not null)
            {
                tableMetadataPb.Comment = Table.Comment;
            }

            if (Table.ExtraConfig.Count > 0)
            {
                foreach (var kvp in Table.ExtraConfig)
                {
                    tableMetadataPb.ExtraConfigs.Add(kvp.Key, kvp.Value);
                }
            }

            proto.TableMetadata = tableMetadataPb;

            // Only include the authz token if the table metadata is included.
            // It is returned in the required GetTableSchema request otherwise.
            var authzToken = Client.GetAuthzToken(Table.TableId);
            if (authzToken is not null)
            {
                proto.AuthzToken = authzToken;
            }
        }
        else
        {
            // If we add the table metadata, we don't need to set the old table id
            // and table name. It is expected that the creation and use of a scan token
            // will be on the same or compatible versions.
            proto.TableId   = Table.TableId;
            proto.TableName = Table.TableName;
        }

        // Map the column names or indices to actual columns in the table schema.
        // If the user did not set either projection, then scan all columns.
        var schema = Table.Schema;

        if (_includeTableMetadata)
        {
            // If the table metadata is included, then the column indexes can be
            // used instead of duplicating the ColumnSchemaPBs in the serialized
            // scan token.
            if (ProjectedColumnNames is not null)
            {
                proto.ProjectedColumnIdx.Capacity = ProjectedColumnNames.Count;

                foreach (var columnName in ProjectedColumnNames)
                {
                    var columnIndex = schema.GetColumnIndex(columnName);
                    proto.ProjectedColumnIdx.Add(columnIndex);
                }
            }
            else if (ProjectedColumnIndexes is not null)
            {
                proto.ProjectedColumnIdx.AddRange(ProjectedColumnIndexes);
            }
            else
            {
                var numColumns = schema.Columns.Count;
                proto.ProjectedColumnIdx.Capacity = numColumns;

                for (int i = 0; i < numColumns; i++)
                {
                    proto.ProjectedColumnIdx.Add(i);
                }
            }
        }
        else
        {
            if (ProjectedColumnNames is not null)
            {
                proto.ProjectedColumns.Capacity = ProjectedColumnNames.Count;

                foreach (var columnName in ProjectedColumnNames)
                {
                    int columnIndex  = schema.GetColumnIndex(columnName);
                    var columnSchema = Table.SchemaPb.Schema.Columns[columnIndex];

                    proto.ProjectedColumns.Add(columnSchema);
                }
            }
            else if (ProjectedColumnIndexes is not null)
            {
                proto.ProjectedColumns.Capacity = ProjectedColumnIndexes.Count;

                foreach (var columnIndex in ProjectedColumnIndexes)
                {
                    var columnSchema = Table.SchemaPb.Schema.Columns[columnIndex];
                    proto.ProjectedColumns.Add(columnSchema);
                }
            }
            else
            {
                proto.ProjectedColumns.AddRange(Table.SchemaPb.Schema.Columns);
            }
        }

        foreach (var predicate in Predicates.Values)
        {
            proto.ColumnPredicates.Add(predicate.ToProtobuf());
        }

        if (LowerBoundPrimaryKey.Length > 0)
        {
            proto.LowerBoundPrimaryKey = UnsafeByteOperations.UnsafeWrap(LowerBoundPrimaryKey);
        }

        if (UpperBoundPrimaryKey.Length > 0)
        {
            proto.UpperBoundPrimaryKey = UnsafeByteOperations.UnsafeWrap(UpperBoundPrimaryKey);
        }

        proto.Limit            = (ulong)Limit;
        proto.ReadMode         = (Protobuf.ReadMode)ReadMode;
        proto.ReplicaSelection = (Protobuf.ReplicaSelection)ReplicaSelection;

        // If the last propagated timestamp is set send it with the scan.
        long lastPropagatedTimestamp = Client.LastPropagatedTimestamp;

        if (lastPropagatedTimestamp != KuduClient.NoTimestamp)
        {
            proto.PropagatedTimestamp = (ulong)lastPropagatedTimestamp;
        }

        // If the mode is set to read on snapshot set the snapshot timestamps.
        if (ReadMode == ReadMode.ReadAtSnapshot)
        {
            if (HtTimestamp != KuduClient.NoTimestamp)
            {
                proto.SnapTimestamp = (ulong)HtTimestamp;
            }
            if (StartTimestamp != KuduClient.NoTimestamp)
            {
                proto.SnapStartTimestamp = (ulong)StartTimestamp;
            }
        }

        proto.CacheBlocks    = CacheBlocks;
        proto.FaultTolerant  = IsFaultTolerant;
        proto.BatchSizeBytes = (uint)BatchSizeBytes;
        // TODO:
        //proto.setScanRequestTimeoutMs(scanRequestTimeout);
        //proto.setKeepAlivePeriodMs(keepAlivePeriodMs);
        //proto.ScanRequestTimeoutMs = 30000;
        //proto.KeepAlivePeriodMs = 15000;

        var pruner = PartitionPruner.Create(
            schema,
            Table.PartitionSchema,
            Predicates,
            LowerBoundPrimaryKey,
            UpperBoundPrimaryKey,
            LowerBoundPartitionKey,
            UpperBoundPartitionKey);

        var keyRanges = new List <KeyRange>();

        while (pruner.HasMorePartitionKeyRanges)
        {
            var partitionRange = pruner.NextPartitionKeyRange;

            var newKeyRanges = await Client.GetTableKeyRangesAsync(
                Table.TableId,
                LowerBoundPrimaryKey,
                UpperBoundPrimaryKey,
                partitionRange.Lower.Length == 0?null : partitionRange.Lower,
                partitionRange.Upper.Length == 0?null : partitionRange.Upper,
                _fetchTabletsPerRangeLookup,
                _splitSizeBytes,
                cancellationToken).ConfigureAwait(false);

            if (newKeyRanges.Count == 0)
            {
                pruner.RemovePartitionKeyRange(partitionRange.Upper);
            }
            else
            {
                pruner.RemovePartitionKeyRange(
                    newKeyRanges[newKeyRanges.Count - 1].PartitionKeyEnd);
            }

            keyRanges.AddRange(newKeyRanges);
        }

        var tokens    = new List <KuduScanToken>(keyRanges.Count);
        var nowMillis = _systemClock.CurrentMilliseconds;

        foreach (var keyRange in keyRanges)
        {
            var token = proto.Clone();

            token.LowerBoundPartitionKey = UnsafeByteOperations.UnsafeWrap(keyRange.PartitionKeyStart);
            token.UpperBoundPartitionKey = UnsafeByteOperations.UnsafeWrap(keyRange.PartitionKeyEnd);

            var primaryKeyStart = keyRange.PrimaryKeyStart;
            if (primaryKeyStart.Length > 0)
            {
                token.LowerBoundPrimaryKey = UnsafeByteOperations.UnsafeWrap(primaryKeyStart);
            }

            var primaryKeyEnd = keyRange.PrimaryKeyEnd;
            if (primaryKeyEnd.Length > 0)
            {
                token.UpperBoundPrimaryKey = UnsafeByteOperations.UnsafeWrap(primaryKeyEnd);
            }

            var tablet = keyRange.Tablet;

            // Set the tablet metadata so that a call to the master is not needed to
            // locate the tablet to scan when opening the scanner.
            if (_includeTabletMetadata)
            {
                // TODO: It would be more efficient to pass the TTL in instead of
                // looking it up again here.
                var entry = Client.GetTableLocationEntry(
                    Table.TableId, tablet.Partition.PartitionKeyStart);

                long ttl;

                if (entry is not null &&
                    entry.IsCoveredRange &&
                    (ttl = entry.Expiration - nowMillis) > 0)
                {
                    // Build the list of server and replica metadata.
                    var tabletServers    = tablet.Servers;
                    var replicas         = tablet.Replicas;
                    var numTabletServers = tabletServers.Count;

                    var tabletMetadataPb = new TabletMetadataPB
                    {
                        TabletId  = tablet.TabletId,
                        Partition = ProtobufHelper.ToPartitionPb(tablet.Partition),
                        TtlMillis = (ulong)ttl
                    };

                    tabletMetadataPb.TabletServers.Capacity = numTabletServers;
                    tabletMetadataPb.Replicas.Capacity      = numTabletServers;

                    for (int i = 0; i < numTabletServers; i++)
                    {
                        var serverInfo       = tabletServers[i];
                        var replica          = replicas[i];
                        var serverMetadataPb = ProtobufHelper.ToServerMetadataPb(serverInfo);

                        tabletMetadataPb.TabletServers.Add(serverMetadataPb);

                        var replicaMetadataPb = new ReplicaMetadataPB
                        {
                            TsIdx = (uint)i,
                            Role  = (RaftPeerPB.Types.Role)replica.Role
                        };

                        if (replica.DimensionLabel is not null)
                        {
                            replicaMetadataPb.DimensionLabel = replica.DimensionLabel;
                        }

                        tabletMetadataPb.Replicas.Add(replicaMetadataPb);
                    }

                    token.TabletMetadata = tabletMetadataPb;
                }
            }

            tokens.Add(new KuduScanToken(keyRange, token));
        }

        return(tokens);
    }