class Dynamoid::AdapterPlugin::AwsSdkV2

The AwsSdkV2 adapter provides support for the aws-sdk version 2 for ruby.

Constants

BINARY_TYPE
EQ
HASH_KEY
NUM_TYPE
RANGE_KEY
RANGE_MAP
STRING_TYPE

Attributes

table_cache[R]

Public Instance Methods

batch_delete_item(options) click to toggle source
Delete many items at once from DynamoDB. More efficient than delete each item individually.

@example Delete IDs 1 and 2 from the table testtable
  Dynamoid::Adapter::AwsSdk.batch_delete_item('table1' => ['1', '2'])

or

  Dynamoid::Adapter::AwsSdkV2.batch_delete_item('table1' => [['hk1', 'rk2'], ['hk1', 'rk2']]]))

@param [Hash] options the hash of tables and IDs to delete

@return nil

@todo: Provide support for passing options to underlying delete_item http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#delete_item-instance_method
# File lib/dynamoid/adapter_plugin/aws_sdk_v2.rb, line 88
def batch_delete_item(options)
  options.each_pair do |table_name, ids|
    table = describe_table(table_name)
    ids.each do |id|
      client.delete_item(table_name: table_name, key: key_stanza(table, *id))
    end
  end
  nil
end
batch_get_item(table_ids, options = {}) click to toggle source

Get many items at once from DynamoDB. More efficient than getting each item individually.

@example Retrieve IDs 1 and 2 from the table testtable

Dynamoid::Adapter::AwsSdkV2.batch_get_item({'table1' => ['1', '2']})

@param [Hash] table_ids the hash of tables and IDs to retrieve @param [Hash] options to be passed to underlying BatchGet call

@return [Hash] a hash where keys are the table names and the values are the retrieved items

@since 1.0.0

@todo: Provide support for passing options to underlying batch_get_item docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#batch_get_item-instance_method

# File lib/dynamoid/adapter_plugin/aws_sdk_v2.rb, line 40
def batch_get_item(table_ids, options = {})
  request_items = Hash.new{|h, k| h[k] = []}
  return request_items if table_ids.all?{|k, v| v.empty?}

  table_ids.each do |t, ids|
    next if ids.empty?
    tbl = describe_table(t)
    hk  = tbl.hash_key.to_s
    rng = tbl.range_key.to_s

    keys = if rng.present?
      Array(ids).map do |h,r|
        { hk => h, rng => r }
      end
    else
      Array(ids).map do |id|
        { hk => id }
      end
    end

    request_items[t] = {
      keys: keys
    }
  end

  results = client.batch_get_item(
    request_items: request_items
  )

  ret = Hash.new([].freeze) # Default for tables where no rows are returned
  results.data[:responses].each do |table, rows|
    ret[table] = rows.collect { |r| result_item_to_hash(r) }
  end
  ret
end
client() click to toggle source

Return the client object.

@since 1.0.0

# File lib/dynamoid/adapter_plugin/aws_sdk_v2.rb, line 23
def client
  @client
end
connect!() click to toggle source

Establish the connection to DynamoDB.

@return [Aws::DynamoDB::Client] the DynamoDB connection

# File lib/dynamoid/adapter_plugin/aws_sdk_v2.rb, line 11
def connect!
  @client = if Dynamoid::Config.endpoint?
    Aws::DynamoDB::Client.new(endpoint: Dynamoid::Config.endpoint)
  else
    Aws::DynamoDB::Client.new
  end
  @table_cache = {}
end
count(table_name) click to toggle source
# File lib/dynamoid/adapter_plugin/aws_sdk_v2.rb, line 427
def count(table_name)
  describe_table(table_name, true).item_count
end
create_table(table_name, key = :id, options = {}) click to toggle source

Create a table on DynamoDB. This usually takes a long time to complete.

@param [String] table_name the name of the table to create @param [Symbol] key the table’s primary key (defaults to :id) @param [Hash] options provide a range key here if the table has a composite key @option options [Array<Dynamoid::Indexes::Index>] local_secondary_indexes @option options [Array<Dynamoid::Indexes::Index>] global_secondary_indexes @option options [Symbol] hash_key_type The type of the hash key @since 1.0.0

# File lib/dynamoid/adapter_plugin/aws_sdk_v2.rb, line 107
def create_table(table_name, key = :id, options = {})
  Dynamoid.logger.info "Creating #{table_name} table. This could take a while."
  read_capacity = options[:read_capacity] || Dynamoid::Config.read_capacity
  write_capacity = options[:write_capacity] || Dynamoid::Config.write_capacity

  secondary_indexes = options.slice(
    :local_secondary_indexes,
    :global_secondary_indexes
  )
  ls_indexes = options[:local_secondary_indexes]
  gs_indexes = options[:global_secondary_indexes]

  key_schema = {
    :hash_key_schema => { key => (options[:hash_key_type] || :string) },
    :range_key_schema => options[:range_key]
  }
  attribute_definitions = build_all_attribute_definitions(
    key_schema,
    secondary_indexes
  )
  key_schema = aws_key_schema(
    key_schema[:hash_key_schema],
    key_schema[:range_key_schema]
  )

  client_opts = {
    table_name: table_name,
    provisioned_throughput: {
      read_capacity_units: read_capacity,
      write_capacity_units: write_capacity
    },
    key_schema: key_schema,
    attribute_definitions: attribute_definitions
  }

  if ls_indexes.present?
    client_opts[:local_secondary_indexes] = ls_indexes.map do |index|
      index_to_aws_hash(index)
    end
  end

  if gs_indexes.present?
    client_opts[:global_secondary_indexes] = gs_indexes.map do |index|
      index_to_aws_hash(index)
    end
  end
  client.create_table(client_opts)
rescue Aws::DynamoDB::Errors::ResourceInUseException => e
  Dynamoid.logger.error "Table #{table_name} cannot be created as it already exists"
end
delete_item(table_name, key, options = {}) click to toggle source

Removes an item from DynamoDB.

@param [String] table_name the name of the table @param [String] key the hash key of the item to delete @param [Hash] options provide a range key here if the table has a composite key

@since 1.0.0

@todo: Provide support for various options docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#delete_item-instance_method

# File lib/dynamoid/adapter_plugin/aws_sdk_v2.rb, line 167
def delete_item(table_name, key, options = {})
  range_key = options[:range_key]
  conditions = options[:conditions]
  table = describe_table(table_name)
  client.delete_item(
    table_name: table_name,
    key: key_stanza(table, key, range_key),
    expected: expected_stanza(conditions)
  )
rescue Aws::DynamoDB::Errors::ConditionalCheckFailedException => e
  raise Dynamoid::Errors::ConditionalCheckFailedException, e
end
delete_table(table_name) click to toggle source

Deletes an entire table from DynamoDB.

@param [String] table_name the name of the table to destroy

@since 1.0.0

# File lib/dynamoid/adapter_plugin/aws_sdk_v2.rb, line 185
def delete_table(table_name)
  client.delete_table(table_name: table_name)
  table_cache.clear
end
get_item(table_name, key, options = {}) click to toggle source

Fetches an item from DynamoDB.

@param [String] table_name the name of the table @param [String] key the hash key of the item to find @param [Hash] options provide a range key here if the table has a composite key

@return [Hash] a hash representing the raw item in DynamoDB

@since 1.0.0

@todo Provide support for various options docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#get_item-instance_method

# File lib/dynamoid/adapter_plugin/aws_sdk_v2.rb, line 203
def get_item(table_name, key, options = {})
  table    = describe_table(table_name)
  range_key = options.delete(:range_key)

  item = client.get_item(table_name: table_name,
    key: key_stanza(table, key, range_key)
  )[:item]
  item ? result_item_to_hash(item) : nil
end
list_tables() click to toggle source

List all tables on DynamoDB.

@since 1.0.0

@todo Provide limit support docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#update_item-instance_method

# File lib/dynamoid/adapter_plugin/aws_sdk_v2.rb, line 248
def list_tables
  client.list_tables[:table_names]
end
put_item(table_name, object, options = nil) click to toggle source

Persists an item on DynamoDB.

@param [String] table_name the name of the table @param [Object] object a hash or Dynamoid object to persist

@since 1.0.0

@todo: Provide support for various options docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#put_item-instance_method

# File lib/dynamoid/adapter_plugin/aws_sdk_v2.rb, line 260
def put_item(table_name, object, options = nil)
  item = {}

  object.each do |k, v|
    next if v.nil? || (v.respond_to?(:empty?) && v.empty?)
    item[k.to_s] = v
  end

  begin
    client.put_item(table_name: table_name,
      item: item,
      expected: expected_stanza(options)
    )
  rescue Aws::DynamoDB::Errors::ConditionalCheckFailedException => e
    raise Dynamoid::Errors::ConditionalCheckFailedException, e
  end
end
query(table_name, opts = {}) click to toggle source

Query the DynamoDB table. This employs DynamoDB’s indexes so is generally faster than scanning, but is only really useful for range queries, since it can only find by one hash key at once. Only provide one range key to the hash.

@param [String] table_name the name of the table @param [Hash] opts the options to query the table with @option opts [String] :hash_value the value of the hash key to find @option opts [Number, Number] :range_between find the range key within this range @option opts [Number] :range_greater_than find range keys greater than this @option opts [Number] :range_less_than find range keys less than this @option opts [Number] :range_gte find range keys greater than or equal to this @option opts [Number] :range_lte find range keys less than or equal to this

@return [Enumerable] matching items

@since 1.0.0

@todo Provide support for various other options docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#query-instance_method

# File lib/dynamoid/adapter_plugin/aws_sdk_v2.rb, line 296
def query(table_name, opts = {})
  table = describe_table(table_name)
  hk    = (opts[:hash_key].present? ? opts[:hash_key] : table.hash_key).to_s
  rng   = (opts[:range_key].present? ? opts[:range_key] : table.range_key).to_s
  q     = opts.slice(
            :consistent_read,
            :scan_index_forward,
            :limit,
            :select,
            :index_name
          )

  opts.delete(:consistent_read)
  opts.delete(:scan_index_forward)
  opts.delete(:limit)
  opts.delete(:select)
  opts.delete(:index_name)

  opts.delete(:next_token).tap do |token|
    break unless token
    q[:exclusive_start_key] = {
      hk  => token[:hash_key_element],
      rng => token[:range_key_element]
    }
  end

  key_conditions = {
    hk => {
      # TODO: Provide option for other operators like NE, IN, LE, etc
      comparison_operator: EQ,
      attribute_value_list: [
        opts.delete(:hash_value).freeze
      ]
    }
  }

  opts.each_pair do |k, v|
    # TODO: ATM, only few comparison operators are supported, provide support for all operators
    next unless(op = RANGE_MAP[k])
    key_conditions[rng] = {
      comparison_operator: op,
      attribute_value_list: [
        opts.delete(k).freeze
      ].flatten # Flatten as BETWEEN operator specifies array of two elements
    }
  end

  q[:table_name]     = table_name
  q[:key_conditions] = key_conditions

  Enumerator.new { |y|
    result = client.query(q)

    result.items.each { |r|
      y << result_item_to_hash(r)
    }
  }
end
scan(table_name, scan_hash, select_opts = {}) click to toggle source

Scan the DynamoDB table. This is usually a very slow operation as it naively filters all data on the DynamoDB servers.

@param [String] table_name the name of the table @param [Hash] scan_hash a hash of attributes: matching records will be returned by the scan

@return [Enumerable] matching items

@since 1.0.0

@todo: Provide support for various options docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#scan-instance_method

# File lib/dynamoid/adapter_plugin/aws_sdk_v2.rb, line 378
def scan(table_name, scan_hash, select_opts = {})
  limit = select_opts.delete(:limit)
  batch = select_opts.delete(:batch_size)

  request = { table_name: table_name }
  request[:limit] = batch || limit if batch || limit
  request[:scan_filter] = scan_hash.reduce({}) do |memo, kvp|
    memo[kvp[0].to_s] = {
      attribute_value_list: [kvp[1]],
      # TODO: Provide support for all comparison operators
      comparison_operator: EQ
    }
    memo
  end if scan_hash.present?

  Enumerator.new do |y|
    # Batch loop, pulls multiple requests until done using the start_key
    loop do
      results = client.scan(request)

      results.data[:items].each { |row| y << result_item_to_hash(row) }

      if((lk = results[:last_evaluated_key]) && batch)
        request[:exclusive_start_key] = lk
      else
        break
      end
    end
  end
end
truncate(table_name) click to toggle source

Truncates all records in the given table

@param [String] table_name the name of the table

@since 1.0.0

# File lib/dynamoid/adapter_plugin/aws_sdk_v2.rb, line 416
def truncate(table_name)
  table = describe_table(table_name)
  hk    = table.hash_key
  rk    = table.range_key

  scan(table_name, {}, {}).each do |attributes|
    opts = {range_key: attributes[rk.to_sym] } if rk
    delete_item(table_name, attributes[hk], opts)
  end
end
update_item(table_name, key, options = {}) { |iu = item_updater| ... } click to toggle source

Edits an existing item’s attributes, or adds a new item to the table if it does not already exist. You can put, delete, or add attribute values

@param [String] table_name the name of the table @param [String] key the hash key of the item to find @param [Hash] options provide a range key here if the table has a composite key

@return new attributes for the record

@todo Provide support for various options docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#update_item-instance_method

# File lib/dynamoid/adapter_plugin/aws_sdk_v2.rb, line 222
def update_item(table_name, key, options = {})
  range_key = options.delete(:range_key)
  conditions = options.delete(:conditions)
  table = describe_table(table_name)

  yield(iu = ItemUpdater.new(table, key, range_key))

  raise "non-empty options: #{options}" unless options.empty?
  begin
    result = client.update_item(table_name: table_name,
      key: key_stanza(table, key, range_key),
      attribute_updates: iu.to_h,
      expected: expected_stanza(conditions),
      return_values: "ALL_NEW"
    )
    result_item_to_hash(result[:attributes])
  rescue Aws::DynamoDB::Errors::ConditionalCheckFailedException => e
    raise Dynamoid::Errors::ConditionalCheckFailedException, e
  end
end

Protected Instance Methods

api_type(type) click to toggle source

Converts from symbol to the API string for the given data type

E.g. :number -> 'N'
# File lib/dynamoid/adapter_plugin/aws_sdk_v2.rb, line 439
def api_type(type)
  case(type)
  when :string then STRING_TYPE
  when :number then NUM_TYPE
  when :binary then BINARY_TYPE
  else raise "Unknown type: #{type}"
  end
end
attribute_definition_element(name, dynamoid_type) click to toggle source

Builds an aws attribute definition based on name and dynamoid type @params [Symbol] name - eg: :id @params [Symbol] dynamoid_type - eg: :string @return [Hash]

# File lib/dynamoid/adapter_plugin/aws_sdk_v2.rb, line 634
def attribute_definition_element(name, dynamoid_type)
  aws_type = api_type(dynamoid_type)

  {
    :attribute_name => name.to_s,
    :attribute_type => aws_type
  }
end
aws_key_schema(hash_key_schema, range_key_schema) click to toggle source

Converts hash_key_schema and range_key_schema to aws_key_schema @param [Hash] hash_key_schema eg: {:id => :string} @param [Hash] range_key_schema eg: {:created_at => :number} @return [Array]

# File lib/dynamoid/adapter_plugin/aws_sdk_v2.rb, line 547
def aws_key_schema(hash_key_schema, range_key_schema)
  schema = [{
    attribute_name: hash_key_schema.keys.first.to_s,
    key_type: HASH_KEY
  }]

  if range_key_schema.present?
    schema << {
      attribute_name: range_key_schema.keys.first.to_s,
      key_type: RANGE_KEY
    }
  end
  schema
end
build_all_attribute_definitions(key_schema, secondary_indexes = {}) click to toggle source

Builds aws attributes definitions based off of primary hash/range and secondary indexes

@param key_data @option key_data [Hash] hash_key_schema - eg: {:id => :string} @option key_data [Hash] range_key_schema - eg: {:created_at => :number} @param [Hash] secondary_indexes @option secondary_indexes [Array<Dynamoid::Indexes::Index>] :local_secondary_indexes @option secondary_indexes [Array<Dynamoid::Indexes::Index>] :global_secondary_indexes

# File lib/dynamoid/adapter_plugin/aws_sdk_v2.rb, line 571
def build_all_attribute_definitions(key_schema, secondary_indexes = {})
  ls_indexes = secondary_indexes[:local_secondary_indexes]
  gs_indexes = secondary_indexes[:global_secondary_indexes]

  attribute_definitions = []

  attribute_definitions << build_attribute_definitions(
    key_schema[:hash_key_schema],
    key_schema[:range_key_schema]
  )

  if ls_indexes.present?
    ls_indexes.map do |index|
      attribute_definitions << build_attribute_definitions(
        index.hash_key_schema,
        index.range_key_schema
      )
    end
  end

  if gs_indexes.present?
    gs_indexes.map do |index|
      attribute_definitions << build_attribute_definitions(
        index.hash_key_schema,
        index.range_key_schema
      )
    end
  end

  attribute_definitions.flatten!
  # uniq these definitions because range keys might be common between
  # primary and secondary indexes
  attribute_definitions.uniq!
  attribute_definitions
end
build_attribute_definitions(hash_key_schema, range_key_schema = nil) click to toggle source

Builds an attribute definitions based on hash key and range key @params [Hash] hash_key_schema - eg: {:id => :string} @params [Hash] range_key_schema - eg: {:created_at => :datetime} @return [Array]

# File lib/dynamoid/adapter_plugin/aws_sdk_v2.rb, line 612
def build_attribute_definitions(hash_key_schema, range_key_schema = nil)
  attrs = []

  attrs << attribute_definition_element(
    hash_key_schema.keys.first,
    hash_key_schema.values.first
  )

  if range_key_schema.present?
    attrs << attribute_definition_element(
      range_key_schema.keys.first,
      range_key_schema.values.first
    )
  end

  attrs
end
describe_table(table_name, reload = false) click to toggle source

New, semi-arbitrary API to get data on the table

# File lib/dynamoid/adapter_plugin/aws_sdk_v2.rb, line 481
def describe_table(table_name, reload = false)
  (!reload && table_cache[table_name]) || begin
    table_cache[table_name] = Table.new(client.describe_table(table_name: table_name).data)
  end
end
expected_stanza(conditions = nil) click to toggle source

@param [Hash] conditions Conditions to enforce on operation (e.g. { :if => { :count => 5 }, :unless_exists => [‘id’]}) @return an Expected stanza for the given conditions hash

# File lib/dynamoid/adapter_plugin/aws_sdk_v2.rb, line 461
def expected_stanza(conditions = nil)
  expected = Hash.new { |h,k| h[k] = {} }
  return expected unless conditions

  conditions[:unless_exists].try(:each) do |col|
    expected[col.to_s][:exists] = false
  end
  conditions[:if].try(:each) do |col,val|
    expected[col.to_s][:value] = val
  end

  expected
end
index_to_aws_hash(index) click to toggle source

Converts a Dynamoid::Indexes::Index to an AWS API-compatible hash. This resulting hash is of the form:

{
  index_name: String
  keys: {
    hash_key: aws_key_schema (hash)
    range_key: aws_key_schema (hash)
  }
  projection: {
    projection_type: (ALL, KEYS_ONLY, INCLUDE) String
    non_key_attributes: (optional) Array
  }
  provisioned_throughput: {
    read_capacity_units: Integer
    write_capacity_units: Integer
  }
}

@param [Dynamoid::Indexes::Index] index the index. @return [Hash] hash representing an AWS Index definition.

# File lib/dynamoid/adapter_plugin/aws_sdk_v2.rb, line 517
def index_to_aws_hash(index)
  key_schema = aws_key_schema(index.hash_key_schema, index.range_key_schema)

  hash = {
    :index_name => index.name,
    :key_schema => key_schema,
    :projection => {
      :projection_type => index.projection_type.to_s.upcase
    }
  }

  # If the projection type is include, specify the non key attributes
  if index.projection_type == :include
    hash[:projection][:non_key_attributes] = index.projected_attributes
  end

  # Only global secondary indexes have a separate throughput.
  if index.type == :global_secondary
    hash[:provisioned_throughput] = {
      :read_capacity_units => index.read_capacity,
      :write_capacity_units => index.write_capacity
    }
  end
  hash
end
key_stanza(table, hash_key, range_key = nil) click to toggle source

The key hash passed on get_item, put_item, delete_item, update_item, etc

# File lib/dynamoid/adapter_plugin/aws_sdk_v2.rb, line 451
def key_stanza(table, hash_key, range_key = nil)
  key = { table.hash_key.to_s => hash_key }
  key[table.range_key.to_s] = range_key if range_key
  key
end
result_item_to_hash(item) click to toggle source

Converts a hash returned by get_item, scan, etc. into a key-value hash

# File lib/dynamoid/adapter_plugin/aws_sdk_v2.rb, line 490
def result_item_to_hash(item)
  {}.tap do |r|
    item.each { |k,v| r[k.to_sym] = v }
  end
end