调用查询操作时发生 dynamo 错误 (ValidationException):无效的 KeyConditionExpression:

error with dynamo occurred (ValidationException) when calling the Query operation: Invalid KeyConditionExpression:

我对 dynamodb 有点陌生

看到我在尝试使用以下 Whosebug post 和 link 中的指令在 python lambda 函数中获取我的 dynamodb table 的最大 ID 时遇到的错误 Dynamodb max value

 An error occurred (ValidationException) when calling the Query operation: Invalid KeyConditionExpression: The expression can not be empty;\"}"

查看下面我的 lambda 函数代码

import json
import boto3

TABLE_NAME = 'user-profiles'

dynamo_DB = boto3.resource('dynamodb')    

def lambda_handler(event, context):
    user_id = event['user_id']
    email = event['email']
    bvn = event['bvn']
    password = event['password']
    phone = event['phone']
    gender = event['gender']

    output = ''


    if len(user_id) > 1 and len(password) > 5:
        try:


            table = dynamo_DB.Table(TABLE_NAME)

            values = list(table.query(
                KeyConditionExpression='',
                ScanIndexForward=False,
                Limit=1
                )
            )
            max_id = values[0]['id']
            new_id = max_id + 1

            Item = {
                'id': str(new_id),
                'profile-id': str(new_id),
                'user_id': user_id,
                'email': email,
                'bvn': bvn,
                'password': password,
                'phone': phone,
                'gender': gender
            }
            table.put_item(Item=Item)

            output += 'Data Inserted To Dynamodb Successfully'
        except Exception as e:
            output += 'error with dynamo registration ' + str(e)
            # print(output)

    else:
        output += 'invalid user or password entered, this is ' \
                  'what i received:\nusername: ' \
                  + str(user_id) + '\npassword: ' + str(password)

    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": output,
        }),
    }
    # print(output)

您不能 query 为空 KeyConditionExpression,如果您需要读取 table 中的所有记录,则需要使用 scan。但是你不能在那里使用 ScanIndexForward 来订购记录。

您似乎在尝试实现主键递增。我想警告你,你的解决方案并不是真的很棒,因为你很容易遇到竞争条件。

我的建议是: 我猜您正在使用 id 作为主键(又名分区键)。没关系。我要做的是在 table 中插入一条额外的记录,比如 increment 值:

increment = table.update_item(
    Key={'id': 'increment'},
    UpdateExpression='ADD #increment :increment',
    ExpressionAttributeNames={'#increment': 'increment'},
    ExpressionAttributeValues={':increment': 1},
    ReturnValues='UPDATED_NEW',
)

new_id = increment['Attributes']['increment']

此查询将使用 id: 'increment' 更新现有记录并在记录中存储新的递增数字,如果这是第一个查询,将使用 increment: 1 创建记录,后续调用将增加它。 ReturnValues 表示查询将 return 更新后的结果,您将获得一个新的 ID。

将代码放在适当的位置,而不是 query 最后一条记录

所以你的代码看起来像:

import json
import boto3

TABLE_NAME = 'user-profiles'

dynamo_DB = boto3.resource('dynamodb')    

def lambda_handler(event, context):
    user_id = event['user_id']
    email = event['email']
    bvn = event['bvn']
    password = event['password']
    phone = event['phone']
    gender = event['gender']

    output = ''


    if len(user_id) > 1 and len(password) > 5:
        try:


            table = dynamo_DB.Table(TABLE_NAME)

            increment = table.update_item(
                Key={'id': 'increment'},
                UpdateExpression='ADD #increment :increment',
                ExpressionAttributeNames={'#increment': 'increment'},
                ExpressionAttributeValues={':increment': 1},
                ReturnValues='UPDATED_NEW',
            )

            new_id = increment['Attributes']['increment']

            Item = {
                'id': str(new_id),
                'profile-id': str(new_id),
                'user_id': user_id,
                'email': email,
                'bvn': bvn,
                'password': password,
                'phone': phone,
                'gender': gender
            }
            table.put_item(Item=Item)

            output += 'Data Inserted To Dynamodb Successfully'
        except Exception as e:
            output += 'error with dynamo registration ' + str(e)
            # print(output)

    else:
        output += 'invalid user or password entered, this is ' \
                  'what i received:\nusername: ' \
                  + str(user_id) + '\npassword: ' + str(password)

    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": output,
        }),
    }
    # print(output)

你很好。

额外的想法:

并且要 100% 确保在递增时不存在竞争条件,您可以通过这种方式实现锁定机制:在递增之前,放置一个额外的记录 idlocklock属性任意值,并使用ConditionExpression='attribute_not_exists(lock)'。然后进行增量,然后通过删除记录 lock 来释放锁。因此,当记录存在时,第二次尝试 'make a lock' 将打破属性 lock 存在的条件并抛出错误 ConditionalCheckFailedException (您可以捕获错误并向用户显示记录被锁定或其他什么。)

这里有一个例子 JavaScript 抱歉:

module.exports.DynamoDbClient = class DynamoDbClient {
  constructor(tableName) {
    this.dynamoDb = new DynamoDB.DocumentClient();
    this.tableName = tableName;
  }

  async increment() {
    await this.lock();

    const {Attributes: {increment}} = await this.dynamoDb.update({
      TableName: this.tableName,
      Key: {id: 'increment'},
      UpdateExpression: 'ADD #increment :increment',
      ExpressionAttributeNames: {'#increment': 'increment'},
      ExpressionAttributeValues: {':increment': 1},
      ReturnValues: 'UPDATED_NEW',
    }).promise();

    await this.unlock();

    return increment;
  }

  async lock(key) {
    try {
      await this.dynamoDb.put({
        TableName: this.tableName,
        Item: {id: 'lock', _lock: true},
        ConditionExpression: 'attribute_not_exists(#lock)',
        ExpressionAttributeNames: {'#lock': '_lock'},
      }).promise();
    } catch (error) {
      if (error.code === 'ConditionalCheckFailedException') {
        throw new LockError(`Key is locked.`);
      }

      throw error;
    }
  }

  unlock() {
    return this.delete({id: 'lock'});
  }

  async delete(key) {
    await this.dynamoDb.delete({
      TableName: this.tableName,
      Key: key,
    }).promise();
  }
}

// usage

const client = new DynamoDbClient('table');
const newId = await client.increment();

...