Better DDB expressions support2: ExpressionTree
Part of structured approach for UpdateExpressions: 1) Expression gets parsed into a tokenlist (tokenized) 2) Tokenlist get transformed to expression tree (AST) -> This commit 3) The AST gets validated (full semantic correctness) 4) AST gets processed to perform the update This commit uses the tokenlist to build an expression tree. This tree is not yet used. Still it allows to raise additional Validation Exceptions which previously were missed silently therefore it allows tests to catch these type of ValidationException. For that reason DDB UpdateExpressions will be parsed already. It also makes sure we won't break existing tests. One of the existing tests had to be changed in order to still pass: - test_dynamodb_table_with_range_key.test_update_item_with_expression This test passed in a numeric literal which is not supported by DynamoDB and with the current tokenization it would get the same error as in AWS DynamoDB.
This commit is contained in:
parent
7ea419dd54
commit
9ed613e197
10 changed files with 2317 additions and 13 deletions
|
|
@ -14,10 +14,11 @@ from moto.core import BaseBackend, BaseModel
|
|||
from moto.core.utils import unix_time
|
||||
from moto.core.exceptions import JsonRESTError
|
||||
from moto.dynamodb2.comparisons import get_filter_expression
|
||||
from moto.dynamodb2.comparisons import get_expected
|
||||
from moto.dynamodb2.exceptions import InvalidIndexNameError, ItemSizeTooLarge
|
||||
from moto.dynamodb2.comparisons import get_expected, get_comparison_func
|
||||
from moto.dynamodb2.exceptions import InvalidIndexNameError, ItemSizeTooLarge, InvalidUpdateExpression
|
||||
from moto.dynamodb2.models.utilities import bytesize, attribute_is_list
|
||||
from moto.dynamodb2.models.dynamo_type import DynamoType
|
||||
from moto.dynamodb2.parsing.expressions import UpdateExpressionParser
|
||||
|
||||
|
||||
class DynamoJsonEncoder(json.JSONEncoder):
|
||||
|
|
@ -1197,6 +1198,13 @@ class DynamoDBBackend(BaseBackend):
|
|||
):
|
||||
table = self.get_table(table_name)
|
||||
|
||||
# Support spaces between operators in an update expression
|
||||
# E.g. `a = b + c` -> `a=b+c`
|
||||
if update_expression:
|
||||
# Parse expression to get validation errors
|
||||
UpdateExpressionParser.make(update_expression)
|
||||
update_expression = re.sub(r"\s*([=\+-])\s*", "\\1", update_expression)
|
||||
|
||||
if all([table.hash_key_attr in key, table.range_key_attr in key]):
|
||||
# Covers cases where table has hash and range keys, ``key`` param
|
||||
# will be a dict
|
||||
|
|
|
|||
205
moto/dynamodb2/parsing/ast_nodes.py
Normal file
205
moto/dynamodb2/parsing/ast_nodes.py
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
import abc
|
||||
import six
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class Node:
|
||||
def __init__(self, children=None):
|
||||
self.type = self.__class__.__name__
|
||||
assert children is None or isinstance(children, list)
|
||||
self.children = children
|
||||
self.parent = None
|
||||
|
||||
if isinstance(children, list):
|
||||
for child in children:
|
||||
if isinstance(child, Node):
|
||||
child.set_parent(self)
|
||||
|
||||
def set_parent(self, parent_node):
|
||||
self.parent = parent_node
|
||||
|
||||
|
||||
class LeafNode(Node):
|
||||
"""A LeafNode is a Node where none of the children are Nodes themselves."""
|
||||
|
||||
def __init__(self, children=None):
|
||||
super(LeafNode, self).__init__(children)
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class Expression(Node):
|
||||
"""
|
||||
Abstract Syntax Tree representing the expression
|
||||
|
||||
For the Grammar start here and jump down into the classes at the righ-hand side to look further. Nodes marked with
|
||||
a star are abstract and won't appear in the final AST.
|
||||
|
||||
Expression* => UpdateExpression
|
||||
Expression* => ConditionExpression
|
||||
"""
|
||||
|
||||
|
||||
class UpdateExpression(Expression):
|
||||
"""
|
||||
UpdateExpression => UpdateExpressionClause*
|
||||
UpdateExpression => UpdateExpressionClause* UpdateExpression
|
||||
"""
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class UpdateExpressionClause(UpdateExpression):
|
||||
"""
|
||||
UpdateExpressionClause* => UpdateExpressionSetClause
|
||||
UpdateExpressionClause* => UpdateExpressionRemoveClause
|
||||
UpdateExpressionClause* => UpdateExpressionAddClause
|
||||
UpdateExpressionClause* => UpdateExpressionDeleteClause
|
||||
"""
|
||||
|
||||
|
||||
class UpdateExpressionSetClause(UpdateExpressionClause):
|
||||
"""
|
||||
UpdateExpressionSetClause => SET SetActions
|
||||
"""
|
||||
|
||||
|
||||
class UpdateExpressionSetActions(UpdateExpressionClause):
|
||||
"""
|
||||
UpdateExpressionSetClause => SET SetActions
|
||||
|
||||
SetActions => SetAction
|
||||
SetActions => SetAction , SetActions
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class UpdateExpressionSetAction(UpdateExpressionClause):
|
||||
"""
|
||||
SetAction => Path = Value
|
||||
"""
|
||||
|
||||
|
||||
class UpdateExpressionRemoveActions(UpdateExpressionClause):
|
||||
"""
|
||||
UpdateExpressionSetClause => REMOVE RemoveActions
|
||||
|
||||
RemoveActions => RemoveAction
|
||||
RemoveActions => RemoveAction , RemoveActions
|
||||
"""
|
||||
|
||||
|
||||
class UpdateExpressionRemoveAction(UpdateExpressionClause):
|
||||
"""
|
||||
RemoveAction => Path
|
||||
"""
|
||||
|
||||
|
||||
class UpdateExpressionAddActions(UpdateExpressionClause):
|
||||
"""
|
||||
UpdateExpressionAddClause => ADD RemoveActions
|
||||
|
||||
AddActions => AddAction
|
||||
AddActions => AddAction , AddActions
|
||||
"""
|
||||
|
||||
|
||||
class UpdateExpressionAddAction(UpdateExpressionClause):
|
||||
"""
|
||||
AddAction => Path Value
|
||||
"""
|
||||
|
||||
|
||||
class UpdateExpressionDeleteActions(UpdateExpressionClause):
|
||||
"""
|
||||
UpdateExpressionDeleteClause => DELETE RemoveActions
|
||||
|
||||
DeleteActions => DeleteAction
|
||||
DeleteActions => DeleteAction , DeleteActions
|
||||
"""
|
||||
|
||||
|
||||
class UpdateExpressionDeleteAction(UpdateExpressionClause):
|
||||
"""
|
||||
DeleteAction => Path Value
|
||||
"""
|
||||
|
||||
|
||||
class UpdateExpressionPath(UpdateExpressionClause):
|
||||
pass
|
||||
|
||||
|
||||
class UpdateExpressionValue(UpdateExpressionClause):
|
||||
"""
|
||||
Value => Operand
|
||||
Value => Operand + Value
|
||||
Value => Operand - Value
|
||||
"""
|
||||
|
||||
|
||||
class UpdateExpressionGroupedValue(UpdateExpressionClause):
|
||||
"""
|
||||
GroupedValue => ( Value )
|
||||
"""
|
||||
|
||||
|
||||
class UpdateExpressionRemoveClause(UpdateExpressionClause):
|
||||
"""
|
||||
UpdateExpressionRemoveClause => REMOVE RemoveActions
|
||||
"""
|
||||
|
||||
|
||||
class UpdateExpressionAddClause(UpdateExpressionClause):
|
||||
"""
|
||||
UpdateExpressionAddClause => ADD AddActions
|
||||
"""
|
||||
|
||||
|
||||
class UpdateExpressionDeleteClause(UpdateExpressionClause):
|
||||
"""
|
||||
UpdateExpressionDeleteClause => DELETE DeleteActions
|
||||
"""
|
||||
|
||||
|
||||
class ExpressionPathDescender(Node):
|
||||
"""Node identifying descender into nested structure (.) in expression"""
|
||||
|
||||
|
||||
class ExpressionSelector(LeafNode):
|
||||
"""Node identifying selector [selection_index] in expresion"""
|
||||
|
||||
def __init__(self, selection_index):
|
||||
super(ExpressionSelector, self).__init__(children=[selection_index])
|
||||
|
||||
|
||||
class ExpressionAttribute(LeafNode):
|
||||
"""An attribute identifier as used in the DDB item"""
|
||||
|
||||
def __init__(self, attribute):
|
||||
super(ExpressionAttribute, self).__init__(children=[attribute])
|
||||
|
||||
|
||||
class ExpressionAttributeName(LeafNode):
|
||||
"""An ExpressionAttributeName is an alias for an attribute identifier"""
|
||||
|
||||
def __init__(self, attribute_name):
|
||||
super(ExpressionAttributeName, self).__init__(children=[attribute_name])
|
||||
|
||||
|
||||
class ExpressionAttributeValue(LeafNode):
|
||||
"""An ExpressionAttributeValue is an alias for an value"""
|
||||
|
||||
def __init__(self, value):
|
||||
super(ExpressionAttributeValue, self).__init__(children=[value])
|
||||
|
||||
|
||||
class ExpressionValueOperator(LeafNode):
|
||||
"""An ExpressionValueOperator is an operation that works on 2 values"""
|
||||
|
||||
def __init__(self, value):
|
||||
super(ExpressionValueOperator, self).__init__(children=[value])
|
||||
|
||||
|
||||
class UpdateExpressionFunction(Node):
|
||||
"""
|
||||
A Node representing a function of an Update Expression. The first child is the function name the others are the
|
||||
arguments.
|
||||
"""
|
||||
1010
moto/dynamodb2/parsing/expressions.py
Normal file
1010
moto/dynamodb2/parsing/expressions.py
Normal file
File diff suppressed because it is too large
Load diff
29
moto/dynamodb2/parsing/reserved_keywords.py
Normal file
29
moto/dynamodb2/parsing/reserved_keywords.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
class ReservedKeywords(list):
|
||||
"""
|
||||
DynamoDB has an extensive list of keywords. Keywords are considered when validating the expression Tree.
|
||||
Not earlier since an update expression like "SET path = VALUE 1" fails with:
|
||||
'Invalid UpdateExpression: Syntax error; token: "1", near: "VALUE 1"'
|
||||
"""
|
||||
|
||||
KEYWORDS = None
|
||||
|
||||
@classmethod
|
||||
def get_reserved_keywords(cls):
|
||||
if cls.KEYWORDS is None:
|
||||
cls.KEYWORDS = cls._get_reserved_keywords()
|
||||
return cls.KEYWORDS
|
||||
|
||||
@classmethod
|
||||
def _get_reserved_keywords(cls):
|
||||
"""
|
||||
Get a list of reserved keywords of DynamoDB
|
||||
"""
|
||||
try:
|
||||
import importlib.resources as pkg_resources
|
||||
except ImportError:
|
||||
import importlib_resources as pkg_resources
|
||||
|
||||
reserved_keywords = pkg_resources.read_text(
|
||||
"moto.dynamodb2.parsing", "reserved_keywords.txt"
|
||||
)
|
||||
return reserved_keywords.split()
|
||||
573
moto/dynamodb2/parsing/reserved_keywords.txt
Normal file
573
moto/dynamodb2/parsing/reserved_keywords.txt
Normal file
|
|
@ -0,0 +1,573 @@
|
|||
ABORT
|
||||
ABSOLUTE
|
||||
ACTION
|
||||
ADD
|
||||
AFTER
|
||||
AGENT
|
||||
AGGREGATE
|
||||
ALL
|
||||
ALLOCATE
|
||||
ALTER
|
||||
ANALYZE
|
||||
AND
|
||||
ANY
|
||||
ARCHIVE
|
||||
ARE
|
||||
ARRAY
|
||||
AS
|
||||
ASC
|
||||
ASCII
|
||||
ASENSITIVE
|
||||
ASSERTION
|
||||
ASYMMETRIC
|
||||
AT
|
||||
ATOMIC
|
||||
ATTACH
|
||||
ATTRIBUTE
|
||||
AUTH
|
||||
AUTHORIZATION
|
||||
AUTHORIZE
|
||||
AUTO
|
||||
AVG
|
||||
BACK
|
||||
BACKUP
|
||||
BASE
|
||||
BATCH
|
||||
BEFORE
|
||||
BEGIN
|
||||
BETWEEN
|
||||
BIGINT
|
||||
BINARY
|
||||
BIT
|
||||
BLOB
|
||||
BLOCK
|
||||
BOOLEAN
|
||||
BOTH
|
||||
BREADTH
|
||||
BUCKET
|
||||
BULK
|
||||
BY
|
||||
BYTE
|
||||
CALL
|
||||
CALLED
|
||||
CALLING
|
||||
CAPACITY
|
||||
CASCADE
|
||||
CASCADED
|
||||
CASE
|
||||
CAST
|
||||
CATALOG
|
||||
CHAR
|
||||
CHARACTER
|
||||
CHECK
|
||||
CLASS
|
||||
CLOB
|
||||
CLOSE
|
||||
CLUSTER
|
||||
CLUSTERED
|
||||
CLUSTERING
|
||||
CLUSTERS
|
||||
COALESCE
|
||||
COLLATE
|
||||
COLLATION
|
||||
COLLECTION
|
||||
COLUMN
|
||||
COLUMNS
|
||||
COMBINE
|
||||
COMMENT
|
||||
COMMIT
|
||||
COMPACT
|
||||
COMPILE
|
||||
COMPRESS
|
||||
CONDITION
|
||||
CONFLICT
|
||||
CONNECT
|
||||
CONNECTION
|
||||
CONSISTENCY
|
||||
CONSISTENT
|
||||
CONSTRAINT
|
||||
CONSTRAINTS
|
||||
CONSTRUCTOR
|
||||
CONSUMED
|
||||
CONTINUE
|
||||
CONVERT
|
||||
COPY
|
||||
CORRESPONDING
|
||||
COUNT
|
||||
COUNTER
|
||||
CREATE
|
||||
CROSS
|
||||
CUBE
|
||||
CURRENT
|
||||
CURSOR
|
||||
CYCLE
|
||||
DATA
|
||||
DATABASE
|
||||
DATE
|
||||
DATETIME
|
||||
DAY
|
||||
DEALLOCATE
|
||||
DEC
|
||||
DECIMAL
|
||||
DECLARE
|
||||
DEFAULT
|
||||
DEFERRABLE
|
||||
DEFERRED
|
||||
DEFINE
|
||||
DEFINED
|
||||
DEFINITION
|
||||
DELETE
|
||||
DELIMITED
|
||||
DEPTH
|
||||
DEREF
|
||||
DESC
|
||||
DESCRIBE
|
||||
DESCRIPTOR
|
||||
DETACH
|
||||
DETERMINISTIC
|
||||
DIAGNOSTICS
|
||||
DIRECTORIES
|
||||
DISABLE
|
||||
DISCONNECT
|
||||
DISTINCT
|
||||
DISTRIBUTE
|
||||
DO
|
||||
DOMAIN
|
||||
DOUBLE
|
||||
DROP
|
||||
DUMP
|
||||
DURATION
|
||||
DYNAMIC
|
||||
EACH
|
||||
ELEMENT
|
||||
ELSE
|
||||
ELSEIF
|
||||
EMPTY
|
||||
ENABLE
|
||||
END
|
||||
EQUAL
|
||||
EQUALS
|
||||
ERROR
|
||||
ESCAPE
|
||||
ESCAPED
|
||||
EVAL
|
||||
EVALUATE
|
||||
EXCEEDED
|
||||
EXCEPT
|
||||
EXCEPTION
|
||||
EXCEPTIONS
|
||||
EXCLUSIVE
|
||||
EXEC
|
||||
EXECUTE
|
||||
EXISTS
|
||||
EXIT
|
||||
EXPLAIN
|
||||
EXPLODE
|
||||
EXPORT
|
||||
EXPRESSION
|
||||
EXTENDED
|
||||
EXTERNAL
|
||||
EXTRACT
|
||||
FAIL
|
||||
FALSE
|
||||
FAMILY
|
||||
FETCH
|
||||
FIELDS
|
||||
FILE
|
||||
FILTER
|
||||
FILTERING
|
||||
FINAL
|
||||
FINISH
|
||||
FIRST
|
||||
FIXED
|
||||
FLATTERN
|
||||
FLOAT
|
||||
FOR
|
||||
FORCE
|
||||
FOREIGN
|
||||
FORMAT
|
||||
FORWARD
|
||||
FOUND
|
||||
FREE
|
||||
FROM
|
||||
FULL
|
||||
FUNCTION
|
||||
FUNCTIONS
|
||||
GENERAL
|
||||
GENERATE
|
||||
GET
|
||||
GLOB
|
||||
GLOBAL
|
||||
GO
|
||||
GOTO
|
||||
GRANT
|
||||
GREATER
|
||||
GROUP
|
||||
GROUPING
|
||||
HANDLER
|
||||
HASH
|
||||
HAVE
|
||||
HAVING
|
||||
HEAP
|
||||
HIDDEN
|
||||
HOLD
|
||||
HOUR
|
||||
IDENTIFIED
|
||||
IDENTITY
|
||||
IF
|
||||
IGNORE
|
||||
IMMEDIATE
|
||||
IMPORT
|
||||
IN
|
||||
INCLUDING
|
||||
INCLUSIVE
|
||||
INCREMENT
|
||||
INCREMENTAL
|
||||
INDEX
|
||||
INDEXED
|
||||
INDEXES
|
||||
INDICATOR
|
||||
INFINITE
|
||||
INITIALLY
|
||||
INLINE
|
||||
INNER
|
||||
INNTER
|
||||
INOUT
|
||||
INPUT
|
||||
INSENSITIVE
|
||||
INSERT
|
||||
INSTEAD
|
||||
INT
|
||||
INTEGER
|
||||
INTERSECT
|
||||
INTERVAL
|
||||
INTO
|
||||
INVALIDATE
|
||||
IS
|
||||
ISOLATION
|
||||
ITEM
|
||||
ITEMS
|
||||
ITERATE
|
||||
JOIN
|
||||
KEY
|
||||
KEYS
|
||||
LAG
|
||||
LANGUAGE
|
||||
LARGE
|
||||
LAST
|
||||
LATERAL
|
||||
LEAD
|
||||
LEADING
|
||||
LEAVE
|
||||
LEFT
|
||||
LENGTH
|
||||
LESS
|
||||
LEVEL
|
||||
LIKE
|
||||
LIMIT
|
||||
LIMITED
|
||||
LINES
|
||||
LIST
|
||||
LOAD
|
||||
LOCAL
|
||||
LOCALTIME
|
||||
LOCALTIMESTAMP
|
||||
LOCATION
|
||||
LOCATOR
|
||||
LOCK
|
||||
LOCKS
|
||||
LOG
|
||||
LOGED
|
||||
LONG
|
||||
LOOP
|
||||
LOWER
|
||||
MAP
|
||||
MATCH
|
||||
MATERIALIZED
|
||||
MAX
|
||||
MAXLEN
|
||||
MEMBER
|
||||
MERGE
|
||||
METHOD
|
||||
METRICS
|
||||
MIN
|
||||
MINUS
|
||||
MINUTE
|
||||
MISSING
|
||||
MOD
|
||||
MODE
|
||||
MODIFIES
|
||||
MODIFY
|
||||
MODULE
|
||||
MONTH
|
||||
MULTI
|
||||
MULTISET
|
||||
NAME
|
||||
NAMES
|
||||
NATIONAL
|
||||
NATURAL
|
||||
NCHAR
|
||||
NCLOB
|
||||
NEW
|
||||
NEXT
|
||||
NO
|
||||
NONE
|
||||
NOT
|
||||
NULL
|
||||
NULLIF
|
||||
NUMBER
|
||||
NUMERIC
|
||||
OBJECT
|
||||
OF
|
||||
OFFLINE
|
||||
OFFSET
|
||||
OLD
|
||||
ON
|
||||
ONLINE
|
||||
ONLY
|
||||
OPAQUE
|
||||
OPEN
|
||||
OPERATOR
|
||||
OPTION
|
||||
OR
|
||||
ORDER
|
||||
ORDINALITY
|
||||
OTHER
|
||||
OTHERS
|
||||
OUT
|
||||
OUTER
|
||||
OUTPUT
|
||||
OVER
|
||||
OVERLAPS
|
||||
OVERRIDE
|
||||
OWNER
|
||||
PAD
|
||||
PARALLEL
|
||||
PARAMETER
|
||||
PARAMETERS
|
||||
PARTIAL
|
||||
PARTITION
|
||||
PARTITIONED
|
||||
PARTITIONS
|
||||
PATH
|
||||
PERCENT
|
||||
PERCENTILE
|
||||
PERMISSION
|
||||
PERMISSIONS
|
||||
PIPE
|
||||
PIPELINED
|
||||
PLAN
|
||||
POOL
|
||||
POSITION
|
||||
PRECISION
|
||||
PREPARE
|
||||
PRESERVE
|
||||
PRIMARY
|
||||
PRIOR
|
||||
PRIVATE
|
||||
PRIVILEGES
|
||||
PROCEDURE
|
||||
PROCESSED
|
||||
PROJECT
|
||||
PROJECTION
|
||||
PROPERTY
|
||||
PROVISIONING
|
||||
PUBLIC
|
||||
PUT
|
||||
QUERY
|
||||
QUIT
|
||||
QUORUM
|
||||
RAISE
|
||||
RANDOM
|
||||
RANGE
|
||||
RANK
|
||||
RAW
|
||||
READ
|
||||
READS
|
||||
REAL
|
||||
REBUILD
|
||||
RECORD
|
||||
RECURSIVE
|
||||
REDUCE
|
||||
REF
|
||||
REFERENCE
|
||||
REFERENCES
|
||||
REFERENCING
|
||||
REGEXP
|
||||
REGION
|
||||
REINDEX
|
||||
RELATIVE
|
||||
RELEASE
|
||||
REMAINDER
|
||||
RENAME
|
||||
REPEAT
|
||||
REPLACE
|
||||
REQUEST
|
||||
RESET
|
||||
RESIGNAL
|
||||
RESOURCE
|
||||
RESPONSE
|
||||
RESTORE
|
||||
RESTRICT
|
||||
RESULT
|
||||
RETURN
|
||||
RETURNING
|
||||
RETURNS
|
||||
REVERSE
|
||||
REVOKE
|
||||
RIGHT
|
||||
ROLE
|
||||
ROLES
|
||||
ROLLBACK
|
||||
ROLLUP
|
||||
ROUTINE
|
||||
ROW
|
||||
ROWS
|
||||
RULE
|
||||
RULES
|
||||
SAMPLE
|
||||
SATISFIES
|
||||
SAVE
|
||||
SAVEPOINT
|
||||
SCAN
|
||||
SCHEMA
|
||||
SCOPE
|
||||
SCROLL
|
||||
SEARCH
|
||||
SECOND
|
||||
SECTION
|
||||
SEGMENT
|
||||
SEGMENTS
|
||||
SELECT
|
||||
SELF
|
||||
SEMI
|
||||
SENSITIVE
|
||||
SEPARATE
|
||||
SEQUENCE
|
||||
SERIALIZABLE
|
||||
SESSION
|
||||
SET
|
||||
SETS
|
||||
SHARD
|
||||
SHARE
|
||||
SHARED
|
||||
SHORT
|
||||
SHOW
|
||||
SIGNAL
|
||||
SIMILAR
|
||||
SIZE
|
||||
SKEWED
|
||||
SMALLINT
|
||||
SNAPSHOT
|
||||
SOME
|
||||
SOURCE
|
||||
SPACE
|
||||
SPACES
|
||||
SPARSE
|
||||
SPECIFIC
|
||||
SPECIFICTYPE
|
||||
SPLIT
|
||||
SQL
|
||||
SQLCODE
|
||||
SQLERROR
|
||||
SQLEXCEPTION
|
||||
SQLSTATE
|
||||
SQLWARNING
|
||||
START
|
||||
STATE
|
||||
STATIC
|
||||
STATUS
|
||||
STORAGE
|
||||
STORE
|
||||
STORED
|
||||
STREAM
|
||||
STRING
|
||||
STRUCT
|
||||
STYLE
|
||||
SUB
|
||||
SUBMULTISET
|
||||
SUBPARTITION
|
||||
SUBSTRING
|
||||
SUBTYPE
|
||||
SUM
|
||||
SUPER
|
||||
SYMMETRIC
|
||||
SYNONYM
|
||||
SYSTEM
|
||||
TABLE
|
||||
TABLESAMPLE
|
||||
TEMP
|
||||
TEMPORARY
|
||||
TERMINATED
|
||||
TEXT
|
||||
THAN
|
||||
THEN
|
||||
THROUGHPUT
|
||||
TIME
|
||||
TIMESTAMP
|
||||
TIMEZONE
|
||||
TINYINT
|
||||
TO
|
||||
TOKEN
|
||||
TOTAL
|
||||
TOUCH
|
||||
TRAILING
|
||||
TRANSACTION
|
||||
TRANSFORM
|
||||
TRANSLATE
|
||||
TRANSLATION
|
||||
TREAT
|
||||
TRIGGER
|
||||
TRIM
|
||||
TRUE
|
||||
TRUNCATE
|
||||
TTL
|
||||
TUPLE
|
||||
TYPE
|
||||
UNDER
|
||||
UNDO
|
||||
UNION
|
||||
UNIQUE
|
||||
UNIT
|
||||
UNKNOWN
|
||||
UNLOGGED
|
||||
UNNEST
|
||||
UNPROCESSED
|
||||
UNSIGNED
|
||||
UNTIL
|
||||
UPDATE
|
||||
UPPER
|
||||
URL
|
||||
USAGE
|
||||
USE
|
||||
USER
|
||||
USERS
|
||||
USING
|
||||
UUID
|
||||
VACUUM
|
||||
VALUE
|
||||
VALUED
|
||||
VALUES
|
||||
VARCHAR
|
||||
VARIABLE
|
||||
VARIANCE
|
||||
VARINT
|
||||
VARYING
|
||||
VIEW
|
||||
VIEWS
|
||||
VIRTUAL
|
||||
VOID
|
||||
WAIT
|
||||
WHEN
|
||||
WHENEVER
|
||||
WHERE
|
||||
WHILE
|
||||
WINDOW
|
||||
WITH
|
||||
WITHIN
|
||||
WITHOUT
|
||||
WORK
|
||||
WRAPPED
|
||||
WRITE
|
||||
YEAR
|
||||
ZONE
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import re
|
||||
import sys
|
||||
|
||||
from moto.dynamodb2.exceptions import (
|
||||
InvalidTokenException,
|
||||
|
|
@ -147,9 +148,17 @@ class ExpressionTokenizer(object):
|
|||
self.token_list = []
|
||||
self.staged_characters = ""
|
||||
|
||||
@classmethod
|
||||
def is_py2(cls):
|
||||
return sys.version_info[0] == 2
|
||||
|
||||
@classmethod
|
||||
def make_list(cls, input_expression_str):
|
||||
assert isinstance(input_expression_str, str)
|
||||
if cls.is_py2():
|
||||
pass
|
||||
else:
|
||||
assert isinstance(input_expression_str, str)
|
||||
|
||||
return ExpressionTokenizer(input_expression_str)._make_list()
|
||||
|
||||
def add_token(self, token_type, token_value):
|
||||
|
|
@ -159,6 +168,10 @@ class ExpressionTokenizer(object):
|
|||
self.add_token(token_type, self.staged_characters)
|
||||
self.staged_characters = ""
|
||||
|
||||
@classmethod
|
||||
def is_numeric(cls, input_str):
|
||||
return re.compile("[0-9]+").match(input_str) is not None
|
||||
|
||||
def process_staged_characters(self):
|
||||
if len(self.staged_characters) == 0:
|
||||
return
|
||||
|
|
@ -167,7 +180,7 @@ class ExpressionTokenizer(object):
|
|||
self.add_token_from_stage(Token.ATTRIBUTE_NAME)
|
||||
else:
|
||||
raise InvalidExpressionAttributeNameKey(self.staged_characters)
|
||||
elif self.staged_characters.isnumeric():
|
||||
elif self.is_numeric(self.staged_characters):
|
||||
self.add_token_from_stage(Token.NUMBER)
|
||||
elif self.is_expression_attribute(self.staged_characters):
|
||||
self.add_token_from_stage(Token.ATTRIBUTE)
|
||||
|
|
|
|||
|
|
@ -748,11 +748,6 @@ class DynamoHandler(BaseResponse):
|
|||
expression_attribute_names = self.body.get("ExpressionAttributeNames", {})
|
||||
expression_attribute_values = self.body.get("ExpressionAttributeValues", {})
|
||||
|
||||
# Support spaces between operators in an update expression
|
||||
# E.g. `a = b + c` -> `a=b+c`
|
||||
if update_expression:
|
||||
update_expression = re.sub(r"\s*([=\+-])\s*", "\\1", update_expression)
|
||||
|
||||
try:
|
||||
item = self.dynamodb_backend.update_item(
|
||||
name,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue