mirror of
https://github.com/redis/redis.git
synced 2026-02-03 20:39:54 -05:00
Optimistic locking for string objects - compare-and-set and compare-and-delete (#14435)
# Description
Add optimistic locking for string objects via compare-and-set and
compare-and-delete mechanism.
## What's changed
Introduction of new DIGEST command for string objects calculated via
XXH3 hash.
Extend SET command with new parameters supporting optimistic locking.
The new value is set only if checks against a given (old) value or a
given string digest pass.
Introduction of new DELEX command to support conditionally deleting a
key. Conditions are also checks against string value or string digest.
## Motivation
For developers who need to to implement a compare-and-set and
compare-and-delete single-key optimistic concurrency control this PR
provides single-command based implementation.
Compare-and-set and compare-and-delete are mostly used for [Optimistic
concurrency
control](https://en.wikipedia.org/wiki/Optimistic_concurrency_control):
a client (1) fetches the value, keeps the old value (or its digest, for
a large string) in memory, (2) manipulates a local copy of the value,
(3) applies the local changes to the server, but only if the server’s
value hasn’t been changed (still equal to the old value).
Note that compare-and-set [can also be
implemented](https://redis.io/docs/latest/develop/using-commands/transactions/#optimistic-locking-using-check-and-set)
with WATCH … MULTI … EXEC and Lua scripts. The new SET optional
arguments and the DELEX command do not enable new functionality,
however, they are much simpler and faster to use for the very common use
case of single-key optimistic concurrency control.
## Related issues and PRs
https://github.com/redis/redis/issues/12485
https://github.com/redis/redis/pull/8361
https://github.com/redis/redis/pull/4258
## Description of the new commands
### DIGEST
```
DIGEST key
```
Get the hash digest of the value stored in key, as an hex string.
Reply:
- Null if key does not exist
- error if key exists but holds a value which is not a string
- (bulk string) the XXH3 digest of the value stored in key, as an hex
string
### SET
```
SET key value [NX | XX | IFEQ match-value | IFNE match-value | IFDEQ match-digest | IFDNE match-digest] [GET] [EX seconds | PX milliseconds | EXAT unix-time-seconds | PXAT unix-time-milliseconds | KEEPTTL]
```
`IFEQ match-value` - Set the key’s value and expiration only if its
current value is equal to match-value. If key doesn’t exist - it won’t
be created.
`IFNE match-value` - Set the key’s value and expiration only if its
current value is not equal to match-value. If key doesn’t exist - it
will be created.
`IFDEQ match-digest` - Set the key’s value and expiration only if the
digest of its current value is equal to match-digest. If key doesn’t
exist - it won’t be created.
`IFDNE match-digest` - Set the key’s value and expiration only if the
digest of its current value is not equal to match-digest. If key doesn’t
exist - it will be created.
Reply update:
- If GET was not specified:
- Nil reply if either
- the key doesn’t exist and XX/IFEQ/IFDEQ was specified. The key was not
created.
- the key exists, and NX was specified or a specified
IFEQ/IFNE/IFDEQ/IFDNE condition is false. The key was not set.
- Simple string reply: OK: The key was set.
- If GET was specified, any of the following:
- Nil reply: The key didn't exist before this command (whether the key
was created or not).
- Bulk string reply: The previous value of the key (whether the key was
set or not).
### DELEX
```
DELEX key [IFEQ match-value | IFNE match-value | IFDEQ match-digest | IFDNE match-digest]
```
Conditionally removes the specified key. A key is ignored if it does not
exist.
`IFEQ match-value` - Delete the key only if its value is equal to
match-value
`IFNE match-value` - Delete the key only if its value is not equal to
match-value
`IFDEQ match-digest` - Delete the key only if the digest of its value is
equal to match-digest
`IFDNE match-digest` - Delete the key only if the digest of its value is
not equal to match-digest
Reply:
- error if key exists but holds a value that is not a string and
IFEQ/IFNE/IFDEQ/IFDNE is specified.
- (integer) 0 if not deleted (the key does not exist or a specified
IFEQ/IFNE/IFDEQ/IFDNE condition is false), or 1 if deleted.
### Notes
Added copy of xxhash repo to deps -
[version](c961fbe61a)
---------
Co-authored-by: debing.sun <debing.sun@redis.com>
Co-authored-by: Yuan Wang <wangyuancode@163.com>
This commit is contained in:
parent
a01c061866
commit
aed879ad0a
18 changed files with 9775 additions and 32 deletions
7
deps/Makefile
vendored
7
deps/Makefile
vendored
|
|
@ -60,6 +60,7 @@ distclean:
|
|||
-(cd hdr_histogram && $(MAKE) clean) > /dev/null || true
|
||||
-(cd fpconv && $(MAKE) clean) > /dev/null || true
|
||||
-(cd fast_float && $(MAKE) clean) > /dev/null || true
|
||||
-(cd xxhash && $(MAKE) clean) > /dev/null || true
|
||||
-(rm -f .make-*)
|
||||
|
||||
.PHONY: distclean
|
||||
|
|
@ -100,6 +101,12 @@ fast_float: .make-prerequisites
|
|||
|
||||
.PHONY: fast_float
|
||||
|
||||
XXHASH_CFLAGS = -fPIC $(DEPS_CFLAGS)
|
||||
xxhash: .make-prerequisites
|
||||
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
|
||||
cd xxhash && $(MAKE) lib CFLAGS="$(XXHASH_CFLAGS)" LDFLAGS="$(DEPS_LDFLAGS)"
|
||||
.PHONY: xxhash
|
||||
|
||||
ifeq ($(uname_S),SunOS)
|
||||
# Make isinf() available
|
||||
LUA_CFLAGS= -D__C99FEATURES__=1
|
||||
|
|
|
|||
6
deps/xxhash/.gitignore
vendored
Normal file
6
deps/xxhash/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# Ignore:
|
||||
libxxhash.*
|
||||
xxh*sum
|
||||
|
||||
# But include:
|
||||
!libxxhash.pc.in
|
||||
26
deps/xxhash/LICENSE
vendored
Normal file
26
deps/xxhash/LICENSE
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
xxHash Library
|
||||
Copyright (c) 2012-2021 Yann Collet
|
||||
All rights reserved.
|
||||
|
||||
BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
687
deps/xxhash/Makefile
vendored
Normal file
687
deps/xxhash/Makefile
vendored
Normal file
|
|
@ -0,0 +1,687 @@
|
|||
# ################################################################
|
||||
# xxHash Makefile
|
||||
# Copyright (C) 2012-2024 Yann Collet
|
||||
#
|
||||
# GPL v2 License
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# You can contact the author at:
|
||||
# - xxHash homepage: https://www.xxhash.com
|
||||
# - xxHash source repository: https://github.com/Cyan4973/xxHash
|
||||
# ################################################################
|
||||
# xxhsum: provides 32/64 bits hash of one or multiple files, or stdin
|
||||
# ################################################################
|
||||
|
||||
# Version numbers
|
||||
SED ?= sed
|
||||
SED_ERE_OPT ?= -E
|
||||
LIBVER_MAJOR_SCRIPT:=`$(SED) -n '/define XXH_VERSION_MAJOR/s/.*[[:blank:]]\([0-9][0-9]*\).*/\1/p' < xxhash.h`
|
||||
LIBVER_MINOR_SCRIPT:=`$(SED) -n '/define XXH_VERSION_MINOR/s/.*[[:blank:]]\([0-9][0-9]*\).*/\1/p' < xxhash.h`
|
||||
LIBVER_PATCH_SCRIPT:=`$(SED) -n '/define XXH_VERSION_RELEASE/s/.*[[:blank:]]\([0-9][0-9]*\).*/\1/p' < xxhash.h`
|
||||
LIBVER_MAJOR := $(shell echo $(LIBVER_MAJOR_SCRIPT))
|
||||
LIBVER_MINOR := $(shell echo $(LIBVER_MINOR_SCRIPT))
|
||||
LIBVER_PATCH := $(shell echo $(LIBVER_PATCH_SCRIPT))
|
||||
LIBVER := $(LIBVER_MAJOR).$(LIBVER_MINOR).$(LIBVER_PATCH)
|
||||
|
||||
MAKEFLAGS += --no-print-directory
|
||||
CFLAGS ?= -O3
|
||||
DEBUGFLAGS+=-Wall -Wextra -Wconversion -Wcast-qual -Wcast-align -Wshadow \
|
||||
-Wstrict-aliasing=1 -Wswitch-enum -Wdeclaration-after-statement \
|
||||
-Wstrict-prototypes -Wundef -Wpointer-arith -Wformat-security \
|
||||
-Wvla -Wformat=2 -Winit-self -Wfloat-equal -Wwrite-strings \
|
||||
-Wredundant-decls -Wstrict-overflow=2
|
||||
CFLAGS += $(DEBUGFLAGS) $(MOREFLAGS)
|
||||
FLAGS = $(CFLAGS) $(CPPFLAGS)
|
||||
XXHSUM_VERSION = $(LIBVER)
|
||||
|
||||
# Define *.exe as extension for Windows systems
|
||||
ifneq (,$(filter Windows%,$(OS)))
|
||||
EXT =.exe
|
||||
else
|
||||
EXT =
|
||||
endif
|
||||
|
||||
# automatically enable runtime vector dispatch on x86/64 targets
|
||||
detect_x86_arch = $(shell $(CC) -dumpmachine | grep -E 'i[3-6]86|x86_64')
|
||||
ifneq ($(strip $(call detect_x86_arch)),)
|
||||
#note: can be overridden at compile time, by setting DISPATCH=0
|
||||
DISPATCH ?= 1
|
||||
else
|
||||
ifeq ($(DISPATCH),1)
|
||||
$(info "Note: DISPATCH=1 is only supported on x86/x64 targets")
|
||||
endif
|
||||
override DISPATCH := 0
|
||||
endif
|
||||
|
||||
ifeq ($(NODE_JS),1)
|
||||
# Link in unrestricted filesystem support
|
||||
LDFLAGS += -sNODERAWFS
|
||||
# Set flag to fix isatty() support
|
||||
CPPFLAGS += -DXSUM_NODE_JS=1
|
||||
endif
|
||||
|
||||
# OS X linker doesn't support -soname, and use different extension
|
||||
# see: https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/DynamicLibraries/100-Articles/DynamicLibraryDesignGuidelines.html
|
||||
UNAME ?= $(shell uname)
|
||||
ifeq ($(UNAME), Darwin)
|
||||
SHARED_EXT = dylib
|
||||
SHARED_EXT_MAJOR = $(LIBVER_MAJOR).$(SHARED_EXT)
|
||||
SHARED_EXT_VER = $(LIBVER).$(SHARED_EXT)
|
||||
SONAME_FLAGS = -install_name $(LIBDIR)/libxxhash.$(SHARED_EXT_MAJOR) -compatibility_version $(LIBVER_MAJOR) -current_version $(LIBVER)
|
||||
else
|
||||
SONAME_FLAGS = -Wl,-soname=libxxhash.$(SHARED_EXT).$(LIBVER_MAJOR)
|
||||
SHARED_EXT = so
|
||||
SHARED_EXT_MAJOR = $(SHARED_EXT).$(LIBVER_MAJOR)
|
||||
SHARED_EXT_VER = $(SHARED_EXT).$(LIBVER)
|
||||
endif
|
||||
|
||||
LIBXXH = libxxhash.$(SHARED_EXT_VER)
|
||||
|
||||
CLI_DIR = cli
|
||||
CLI_SRCS = $(wildcard $(CLI_DIR)/*.c)
|
||||
CLI_OBJS = $(CLI_SRCS:.c=.o)
|
||||
|
||||
## define default before including multiconf.make
|
||||
## generate CLI and libraries in release mode (default for `make`)
|
||||
.PHONY: default
|
||||
default: DEBUGFLAGS=
|
||||
default: lib xxhsum_and_links
|
||||
|
||||
C_SRCDIRS = . $(CLI_DIR) fuzz
|
||||
include build/make/multiconf.make
|
||||
|
||||
.PHONY: all
|
||||
all: lib xxhsum xxhsum_inlinedXXH
|
||||
|
||||
## xxhsum is the command line interface (CLI)
|
||||
ifeq ($(DISPATCH),1)
|
||||
xxhsum: CPPFLAGS += -DXXHSUM_DISPATCH=1
|
||||
XXHSUM_ADD_O = xxh_x86dispatch.o
|
||||
endif
|
||||
$(eval $(call c_program,xxhsum,xxhash.o $(CLI_OBJS) $(XXHSUM_ADD_O)))
|
||||
|
||||
.PHONY: xxhsum_and_links
|
||||
xxhsum_and_links: xxhsum xxh32sum xxh64sum xxh128sum xxh3sum
|
||||
|
||||
LN ?= ln
|
||||
|
||||
xxh32sum xxh64sum xxh128sum xxh3sum: xxhsum
|
||||
$(LN) -sf $<$(EXT) $@$(EXT)
|
||||
|
||||
## generate CLI in 32-bits mode
|
||||
xxhsum32: CFLAGS += -m32
|
||||
ifeq ($(DISPATCH),1)
|
||||
xxhsum32: CPPFLAGS += -DXXHSUM_DISPATCH=1
|
||||
endif
|
||||
$(eval $(call c_program,xxhsum32,xxhash.o $(CLI_OBJS) $(XXHSUM_ADD_O)))
|
||||
|
||||
## Warning: dispatch only works for x86/x64 systems
|
||||
dispatch: CPPFLAGS += -DXXHSUM_DISPATCH=1
|
||||
$(eval $(call c_program,dispatch,xxhash.o xxh_x86dispatch.o $(CLI_OBJS)))
|
||||
|
||||
xxhsum_inlinedXXH: CPPFLAGS += -DXXH_INLINE_ALL
|
||||
$(eval $(call c_program,xxhsum_inlinedXXH,$(CLI_OBJS)))
|
||||
|
||||
|
||||
# =================================================
|
||||
# library
|
||||
|
||||
libxxhash.a:
|
||||
$(eval $(call static_library,libxxhash.a,xxhash.o))
|
||||
|
||||
$(LIBXXH): LDFLAGS += $(SONAME_FLAGS)
|
||||
ifeq (,$(filter Windows%,$(OS)))
|
||||
$(LIBXXH): CFLAGS += -fPIC
|
||||
endif
|
||||
LIBXXHASH_OBJS := xxhash.o $(if $(filter 1,$(LIBXXH_DISPATCH)),xxh_x86dispatch.o)
|
||||
$(eval $(call c_dynamic_library,$(LIBXXH),$(LIBXXHASH_OBJS)))
|
||||
|
||||
libxxhash.$(SHARED_EXT_MAJOR): $(LIBXXH)
|
||||
$(LN) -sf $< $@
|
||||
|
||||
libxxhash.$(SHARED_EXT): libxxhash.$(SHARED_EXT_MAJOR)
|
||||
$(LN) -sf $< $@
|
||||
|
||||
.PHONY: libxxhash ## generate dynamic xxhash library
|
||||
libxxhash: $(LIBXXH) libxxhash.$(SHARED_EXT_MAJOR) libxxhash.$(SHARED_EXT)
|
||||
|
||||
.PHONY: lib ## generate static and dynamic xxhash libraries
|
||||
lib: libxxhash.a libxxhash
|
||||
|
||||
|
||||
# helper targets
|
||||
|
||||
AWK ?= awk
|
||||
GREP ?= grep
|
||||
SORT ?= sort
|
||||
NM ?= nm
|
||||
|
||||
.PHONY: list
|
||||
list: ## list all Makefile targets
|
||||
$(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | $(AWK) -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | $(SORT) | egrep -v -e '^[^[:alnum:]]' -e '^$@$$' | xargs
|
||||
|
||||
.PHONY: help
|
||||
help: ## list documented targets
|
||||
$(GREP) -E '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
|
||||
$(SORT) | \
|
||||
$(AWK) 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
$(RM) -r *.dSYM # Mac OS-X specific
|
||||
$(RM) core *.o *.obj *.$(SHARED_EXT) *.$(SHARED_EXT).* *.a libxxhash.pc
|
||||
$(RM) xxhsum.wasm xxhsum.js xxhsum.html
|
||||
$(RM) xxh32sum$(EXT) xxh64sum$(EXT) xxh128sum$(EXT) xxh3sum$(EXT)
|
||||
$(RM) fuzzer
|
||||
$(MAKE) -C tests clean
|
||||
$(MAKE) -C tests/bench clean
|
||||
$(MAKE) -C tests/collisions clean
|
||||
@echo cleaning completed
|
||||
|
||||
|
||||
# =================================================
|
||||
# tests
|
||||
# =================================================
|
||||
|
||||
# make check can be run with cross-compiled binaries on emulated environments (qemu user mode)
|
||||
# by setting $(RUN_ENV) to the target emulation environment
|
||||
.PHONY: check
|
||||
check: xxhsum test_sanity ## basic tests for xxhsum CLI, set RUN_ENV for emulated environments
|
||||
# stdin
|
||||
# If you get "Wrong parameters" on Emscripten+Node.js, recompile with `NODE_JS=1`
|
||||
$(RUN_ENV) ./xxhsum$(EXT) < xxhash.c
|
||||
# multiple files
|
||||
$(RUN_ENV) ./xxhsum$(EXT) xxhash.*
|
||||
# internal bench
|
||||
$(RUN_ENV) ./xxhsum$(EXT) -bi0
|
||||
# long bench command
|
||||
$(RUN_ENV) ./xxhsum$(EXT) --benchmark-all -i0
|
||||
# bench multiple variants
|
||||
$(RUN_ENV) ./xxhsum$(EXT) -b1,2,3 -i0
|
||||
# file bench
|
||||
$(RUN_ENV) ./xxhsum$(EXT) -bi0 xxhash.c
|
||||
# 32-bit
|
||||
$(RUN_ENV) ./xxhsum$(EXT) -H0 xxhash.c
|
||||
# 128-bit
|
||||
$(RUN_ENV) ./xxhsum$(EXT) -H2 xxhash.c
|
||||
# XXH3 (enforce BSD style)
|
||||
$(RUN_ENV) ./xxhsum$(EXT) -H3 xxhash.c | grep "XXH3"
|
||||
# request incorrect variant
|
||||
$(RUN_ENV) ./xxhsum$(EXT) -H9 xxhash.c ; test $$? -eq 1
|
||||
@printf "\n ....... checks completed successfully ....... \n"
|
||||
|
||||
.PHONY: test-unicode
|
||||
test-unicode:
|
||||
$(MAKE) -C tests test_unicode
|
||||
|
||||
.PHONY: test_sanity
|
||||
test_sanity:
|
||||
$(MAKE) -C tests test_sanity
|
||||
|
||||
.PHONY: test-mem
|
||||
VALGRIND = valgrind --leak-check=yes --error-exitcode=1
|
||||
test-mem: RUN_ENV = $(VALGRIND)
|
||||
test-mem: xxhsum check
|
||||
|
||||
.PHONY: test32
|
||||
test32: xxhsum32
|
||||
@echo ---- test 32-bit ----
|
||||
./xxhsum32 -bi0 xxhash.c
|
||||
|
||||
TEST_FILES = xxhsum$(EXT) xxhash.c xxhash.h
|
||||
.PHONY: test-xxhsum-c
|
||||
test-xxhsum-c: xxhsum
|
||||
# xxhsum to/from pipe
|
||||
./xxhsum $(TEST_FILES) | ./xxhsum -c -
|
||||
./xxhsum -H0 $(TEST_FILES) | ./xxhsum -c -
|
||||
# xxhsum -c is unable to verify checksum of file from STDIN (#470)
|
||||
./xxhsum < README.md > .test.README.md.xxh
|
||||
./xxhsum -c .test.README.md.xxh < README.md
|
||||
# xxhsum -q does not display "Loading" message into stderr (#251)
|
||||
! ./xxhsum -q $(TEST_FILES) 2>&1 | grep Loading
|
||||
# xxhsum does not display "Loading" message into stderr either
|
||||
! ./xxhsum $(TEST_FILES) 2>&1 | grep Loading
|
||||
# Check that xxhsum do display filename that it failed to open.
|
||||
LC_ALL=C ./xxhsum nonexistent 2>&1 | grep "Error: Could not open 'nonexistent'"
|
||||
# xxhsum to/from file, shell redirection
|
||||
./xxhsum $(TEST_FILES) > .test.xxh64
|
||||
./xxhsum --tag $(TEST_FILES) > .test.xxh64_tag
|
||||
./xxhsum --little-endian $(TEST_FILES) > .test.le_xxh64
|
||||
./xxhsum --tag --little-endian $(TEST_FILES) > .test.le_xxh64_tag
|
||||
./xxhsum -H0 $(TEST_FILES) > .test.xxh32
|
||||
./xxhsum -H0 --tag $(TEST_FILES) > .test.xxh32_tag
|
||||
./xxhsum -H0 --little-endian $(TEST_FILES) > .test.le_xxh32
|
||||
./xxhsum -H0 --tag --little-endian $(TEST_FILES) > .test.le_xxh32_tag
|
||||
./xxhsum -H2 $(TEST_FILES) > .test.xxh128
|
||||
./xxhsum -H2 --tag $(TEST_FILES) > .test.xxh128_tag
|
||||
./xxhsum -H2 --little-endian $(TEST_FILES) > .test.le_xxh128
|
||||
./xxhsum -H2 --tag --little-endian $(TEST_FILES) > .test.le_xxh128_tag
|
||||
./xxhsum -H3 $(TEST_FILES) > .test.xxh3
|
||||
./xxhsum -H3 --tag $(TEST_FILES) > .test.xxh3_tag
|
||||
./xxhsum -H3 --little-endian $(TEST_FILES) > .test.le_xxh3
|
||||
./xxhsum -H3 --tag --little-endian $(TEST_FILES) > .test.le_xxh3_tag
|
||||
./xxhsum -c .test.xxh*
|
||||
./xxhsum -c --little-endian .test.le_xxh*
|
||||
./xxhsum -c .test.*_tag
|
||||
# read list of files from stdin
|
||||
./xxhsum -c < .test.xxh32
|
||||
./xxhsum -c < .test.xxh64
|
||||
./xxhsum -c < .test.xxh128
|
||||
./xxhsum -c < .test.xxh3
|
||||
cat .test.xxh* | ./xxhsum -c -
|
||||
# check variant with '*' marker as second separator
|
||||
$(SED) 's/ / \*/' .test.xxh32 | ./xxhsum -c
|
||||
# bsd-style output
|
||||
./xxhsum --tag xxhsum* | $(GREP) XXH64
|
||||
./xxhsum --tag -H0 xxhsum* | $(GREP) XXH32
|
||||
./xxhsum --tag -H1 xxhsum* | $(GREP) XXH64
|
||||
./xxhsum --tag -H2 xxhsum* | $(GREP) XXH128
|
||||
./xxhsum --tag -H3 xxhsum* | $(GREP) XXH3
|
||||
./xxhsum -H3 xxhsum* | $(GREP) XXH3_ # prefix for GNU format
|
||||
./xxhsum --tag -H32 xxhsum* | $(GREP) XXH32
|
||||
./xxhsum --tag -H64 xxhsum* | $(GREP) XXH64
|
||||
./xxhsum --tag -H128 xxhsum* | $(GREP) XXH128
|
||||
./xxhsum --tag -H0 --little-endian xxhsum* | $(GREP) XXH32_LE
|
||||
./xxhsum --tag -H1 --little-endian xxhsum* | $(GREP) XXH64_LE
|
||||
./xxhsum --tag -H2 --little-endian xxhsum* | $(GREP) XXH128_LE
|
||||
./xxhsum --tag -H3 --little-endian xxhsum* | $(GREP) XXH3_LE
|
||||
./xxhsum --tag -H32 --little-endian xxhsum* | $(GREP) XXH32_LE
|
||||
./xxhsum --tag -H64 --little-endian xxhsum* | $(GREP) XXH64_LE
|
||||
./xxhsum --tag -H128 --little-endian xxhsum* | $(GREP) XXH128_LE
|
||||
# check bsd-style
|
||||
./xxhsum --tag xxhsum* | ./xxhsum -c
|
||||
./xxhsum --tag -H32 --little-endian xxhsum* | ./xxhsum -c
|
||||
# xxhsum -c warns improperly format lines.
|
||||
echo '12345678 ' >>.test.xxh32
|
||||
./xxhsum -c .test.xxh32 | $(GREP) improperly
|
||||
echo '123456789 file' >>.test.xxh64
|
||||
./xxhsum -c .test.xxh64 | $(GREP) improperly
|
||||
# Expects "FAILED"
|
||||
echo "0000000000000000 LICENSE" | ./xxhsum -c -; test $$? -eq 1
|
||||
echo "00000000 LICENSE" | ./xxhsum -c -; test $$? -eq 1
|
||||
# Expects "FAILED open or read"
|
||||
echo "0000000000000000 test-expects-file-not-found" | ./xxhsum -c -; test $$? -eq 1
|
||||
echo "00000000 test-expects-file-not-found" | ./xxhsum -c -; test $$? -eq 1
|
||||
# --filelist
|
||||
echo xxhash.c > .test.filenames
|
||||
$(RUN_ENV) ./xxhsum$(EXT) --filelist .test.filenames
|
||||
# --filelist from stdin
|
||||
cat .test.filenames | $(RUN_ENV) ./xxhsum$(EXT) --filelist
|
||||
@$(RM) .test.*
|
||||
|
||||
CC_VERSION := $(shell $(CC) --version 2>/dev/null)
|
||||
ifneq (,$(findstring clang,$(CC_VERSION)))
|
||||
fuzzer: CFLAGS += -fsanitize=fuzzer
|
||||
$(eval $(call c_program,fuzzer, fuzz/fuzzer.o xxhash.o))
|
||||
else
|
||||
fuzzer: this_target_requires_clang # intentional fail
|
||||
endif
|
||||
|
||||
.PHONY: test-filename-escape
|
||||
test-filename-escape:
|
||||
$(MAKE) -C tests test_filename_escape
|
||||
|
||||
.PHONY: test-cli-comment-line
|
||||
test-cli-comment-line:
|
||||
$(MAKE) -C tests test_cli_comment_line
|
||||
|
||||
.PHONY: test-cli-ignore-missing
|
||||
test-cli-ignore-missing:
|
||||
$(MAKE) -C tests test_cli_ignore_missing
|
||||
|
||||
.PHONY: armtest
|
||||
armtest:
|
||||
@echo ---- test ARM compilation ----
|
||||
CC=arm-linux-gnueabi-gcc MOREFLAGS="-Werror -static" $(MAKE) xxhsum
|
||||
|
||||
.PHONY: arm64test
|
||||
arm64test:
|
||||
@echo ---- test ARM64 compilation ----
|
||||
CC=aarch64-linux-gnu-gcc MOREFLAGS="-Werror -static" $(MAKE) xxhsum
|
||||
|
||||
.PHONY: clangtest
|
||||
clangtest:
|
||||
@echo ---- test clang compilation ----
|
||||
CC=clang MOREFLAGS="-Werror -Wconversion -Wno-sign-conversion" $(MAKE) all
|
||||
|
||||
.PHONY: gcc-og-test
|
||||
gcc-og-test:
|
||||
@echo ---- test gcc -Og compilation ----
|
||||
CFLAGS="-Og -Wall -Wextra -Wundef -Wshadow -Wcast-align -Werror -fPIC" CPPFLAGS="-DXXH_NO_INLINE_HINTS" MOREFLAGS="-Werror" $(MAKE) all
|
||||
|
||||
.PHONY: cxxtest
|
||||
cxxtest:
|
||||
@echo ---- test C++ compilation ----
|
||||
CC="$(CXX) -Wno-deprecated" $(MAKE) all CFLAGS="-O3 -Wall -Wextra -Wundef -Wshadow -Wcast-align -Werror -fPIC"
|
||||
|
||||
# In strict C90 mode, there is no `long long` type support,
|
||||
# consequently, only XXH32 can be compiled.
|
||||
.PHONY: c90test
|
||||
ifeq ($(NO_C90_TEST),true)
|
||||
c90test:
|
||||
@echo no c90 compatibility test
|
||||
else
|
||||
c90test: CPPFLAGS += -DXXH_NO_LONG_LONG
|
||||
c90test: CFLAGS += -std=c90 -Werror -pedantic
|
||||
c90test: xxhash.c
|
||||
@echo ---- test strict C90 compilation [xxh32 only] ----
|
||||
$(RM) xxhash.o
|
||||
$(CC) $(FLAGS) $^ -c
|
||||
$(NM) xxhash.o | $(GREP) XXH64 ; test $$? -eq 1
|
||||
$(RM) xxhash.o
|
||||
endif
|
||||
|
||||
.PHONY: noxxh3test
|
||||
noxxh3test: CPPFLAGS += -DXXH_NO_XXH3
|
||||
noxxh3test: CFLAGS += -Werror -pedantic -Wno-long-long # XXH64 requires long long support
|
||||
noxxh3test: OFILE = xxh_noxxh3.o
|
||||
noxxh3test: xxhash.c
|
||||
@echo ---- test compilation without XXH3 ----
|
||||
$(CC) $(FLAGS) -c $^ -o $(OFILE)
|
||||
$(NM) $(OFILE) | $(GREP) XXH3_ ; test $$? -eq 1
|
||||
$(RM) $(OFILE)
|
||||
|
||||
.PHONY: nostreamtest
|
||||
nostreamtest: CPPFLAGS += -DXXH_NO_STREAM
|
||||
nostreamtest: CFLAGS += -Werror -pedantic -Wno-long-long # XXH64 requires long long support
|
||||
nostreamtest: OFILE = xxh_nostream.o
|
||||
nostreamtest: xxhash.c
|
||||
@echo ---- test compilation without streaming ----
|
||||
$(CC) $(FLAGS) -c $^ -o $(OFILE)
|
||||
$(NM) $(OFILE) | $(GREP) update ; test $$? -eq 1
|
||||
$(RM) $(OFILE)
|
||||
|
||||
.PHONY: nostdlibtest
|
||||
nostdlibtest: CPPFLAGS += -DXXH_NO_STDLIB
|
||||
nostdlibtest: CFLAGS += -Werror -pedantic -Wno-long-long # XXH64 requires long long support
|
||||
nostdlibtest: OFILE = xxh_nostdlib.o
|
||||
nostdlibtest: xxhash.c
|
||||
@echo ---- test compilation without \<stdlib.h\> ----
|
||||
$(CC) $(FLAGS) -c $^ -o $(OFILE)
|
||||
$(NM) $(OFILE) | $(GREP) "U _free\|U free" ; test $$? -eq 1
|
||||
$(RM) $(OFILE)
|
||||
|
||||
.PHONY: usan
|
||||
usan: CC=clang
|
||||
usan: CXX=clang++
|
||||
usan: ## check CLI runtime for undefined behavior, using clang's sanitizer
|
||||
@echo ---- check undefined behavior - sanitize ----
|
||||
$(MAKE) test CC=$(CC) CXX=$(CXX) MOREFLAGS="-g -fsanitize=undefined -fno-sanitize-recover=all"
|
||||
|
||||
.PHONY: staticAnalyze
|
||||
SCANBUILD ?= scan-build
|
||||
staticAnalyze: clean ## check C source files using $(SCANBUILD) static analyzer
|
||||
@echo ---- static analyzer - $(SCANBUILD) ----
|
||||
CFLAGS="-g -Werror" $(SCANBUILD) --status-bugs -v $(MAKE) all
|
||||
|
||||
CPPCHECK ?= cppcheck
|
||||
.PHONY: cppcheck
|
||||
cppcheck: ## check C source files using $(CPPCHECK) static analyzer
|
||||
@echo ---- static analyzer - $(CPPCHECK) ----
|
||||
$(CPPCHECK) . --force --enable=warning,portability,performance,style --error-exitcode=1 > /dev/null
|
||||
|
||||
.PHONY: namespaceTest
|
||||
namespaceTest: ## ensure XXH_NAMESPACE redefines all public symbols
|
||||
$(CC) -c xxhash.c
|
||||
$(CC) -DXXH_NAMESPACE=TEST_ -c xxhash.c -o xxhash2.o
|
||||
$(CC) xxhash.o xxhash2.o $(CLI_SRCS) -o xxhsum2 # will fail if one namespace missing (symbol collision)
|
||||
$(RM) *.o xxhsum2 # clean
|
||||
|
||||
MAN = $(CLI_DIR)/xxhsum.1
|
||||
MD2ROFF ?= ronn
|
||||
MD2ROFF_FLAGS ?= --roff --warnings --manual="User Commands" --organization="xxhsum $(XXHSUM_VERSION)"
|
||||
$(MAN): $(CLI_DIR)/xxhsum.1.md xxhash.h
|
||||
cat $< | $(MD2ROFF) $(MD2ROFF_FLAGS) | $(SED) -n '/^\.\\\".*/!p' > $@
|
||||
|
||||
.PHONY: man
|
||||
man: $(MAN) ## generate man page from markdown source
|
||||
|
||||
.PHONY: clean-man
|
||||
clean-man:
|
||||
$(RM) xxhsum.1
|
||||
|
||||
.PHONY: preview-man
|
||||
preview-man: man
|
||||
man ./xxhsum.1
|
||||
|
||||
.PHONY: test
|
||||
test: DEBUGFLAGS += -DXXH_DEBUGLEVEL=1
|
||||
test: all namespaceTest check test-xxhsum-c c90test test-tools noxxh3test nostdlibtest
|
||||
|
||||
# this test checks that including "xxhash.h" multiple times and with different directives still compiles properly
|
||||
.PHONY: test-multiInclude
|
||||
test-multiInclude:
|
||||
$(MAKE) -C tests test_multiInclude
|
||||
|
||||
.PHONY: test-inline-notexposed
|
||||
test-inline-notexposed: xxhsum_inlinedXXH
|
||||
$(NM) xxhsum_inlinedXXH | $(GREP) "t _XXH32_" ; test $$? -eq 1 # no XXH32 symbol should be left
|
||||
$(NM) xxhsum_inlinedXXH | $(GREP) "t _XXH64_" ; test $$? -eq 1 # no XXH64 symbol should be left
|
||||
|
||||
.PHONY: test-inline
|
||||
test-inline: test-inline-notexposed test-multiInclude
|
||||
|
||||
|
||||
.PHONY: test-all
|
||||
test-all: CFLAGS += -Werror
|
||||
test-all: test test32 test-unicode clangtest gcc-og-test cxxtest usan test-inline listL120 trailingWhitespace test-xxh-nnn-sums
|
||||
|
||||
.PHONY: test-tools
|
||||
test-tools:
|
||||
CFLAGS=-Werror $(MAKE) -C tests/bench
|
||||
CFLAGS=-Werror $(MAKE) -C tests/collisions check
|
||||
|
||||
.PHONY: test-xxh-nnn-sums
|
||||
test-xxh-nnn-sums: xxhsum_and_links
|
||||
./xxhsum README.md > tmp.xxhsum.out # xxhsum outputs xxh64
|
||||
./xxh32sum README.md > tmp.xxh32sum.out
|
||||
./xxh64sum README.md > tmp.xxh64sum.out
|
||||
./xxh128sum README.md > tmp.xxh128sum.out
|
||||
./xxh3sum README.md > tmp.xxh3sum.out
|
||||
cat tmp.xxhsum.out
|
||||
cat tmp.xxh32sum.out
|
||||
cat tmp.xxh64sum.out
|
||||
cat tmp.xxh128sum.out
|
||||
cat tmp.xxh3sum.out
|
||||
./xxhsum -c tmp.xxhsum.out
|
||||
./xxhsum -c tmp.xxh32sum.out
|
||||
./xxhsum -c tmp.xxh64sum.out
|
||||
./xxhsum -c tmp.xxh128sum.out
|
||||
./xxhsum -c tmp.xxh3sum.out
|
||||
./xxh32sum -c tmp.xxhsum.out ; test $$? -eq 1 # expects "no properly formatted"
|
||||
./xxh32sum -c tmp.xxh32sum.out
|
||||
./xxh32sum -c tmp.xxh64sum.out ; test $$? -eq 1 # expects "no properly formatted"
|
||||
./xxh32sum -c tmp.xxh128sum.out ; test $$? -eq 1 # expects "no properly formatted"
|
||||
./xxh32sum -c tmp.xxh3sum.out ; test $$? -eq 1 # expects "no properly formatted"
|
||||
./xxh64sum -c tmp.xxhsum.out
|
||||
./xxh64sum -c tmp.xxh32sum.out ; test $$? -eq 1 # expects "no properly formatted"
|
||||
./xxh64sum -c tmp.xxh64sum.out
|
||||
./xxh64sum -c tmp.xxh128sum.out ; test $$? -eq 1 # expects "no properly formatted"
|
||||
./xxh64sum -c tmp.xxh3sum.out ; test $$? -eq 1 # expects "no properly formatted"
|
||||
./xxh128sum -c tmp.xxhsum.out ; test $$? -eq 1 # expects "no properly formatted"
|
||||
./xxh128sum -c tmp.xxh32sum.out ; test $$? -eq 1 # expects "no properly formatted"
|
||||
./xxh128sum -c tmp.xxh64sum.out ; test $$? -eq 1 # expects "no properly formatted"
|
||||
./xxh128sum -c tmp.xxh128sum.out
|
||||
./xxh128sum -c tmp.xxh3sum.out ; test $$? -eq 1 # expects "no properly formatted"
|
||||
./xxh3sum -c tmp.xxhsum.out ; test $$? -eq 1 # expects "no properly formatted"
|
||||
./xxh3sum -c tmp.xxh32sum.out ; test $$? -eq 1 # expects "no properly formatted"
|
||||
./xxh3sum -c tmp.xxh64sum.out ; test $$? -eq 1 # expects "no properly formatted"
|
||||
./xxh3sum -c tmp.xxh128sum.out ; test $$? -eq 1 # expects "no properly formatted"
|
||||
./xxh3sum -c tmp.xxh3sum.out
|
||||
|
||||
.PHONY: listL120
|
||||
listL120: # extract lines >= 120 characters in *.{c,h}, by Takayuki Matsuoka (note: $$, for Makefile compatibility)
|
||||
find . -type f -name '*.c' -o -name '*.h' | while read -r filename; do awk 'length > 120 {print FILENAME "(" FNR "): " $$0}' $$filename; done
|
||||
|
||||
.PHONY: trailingWhitespace
|
||||
trailingWhitespace:
|
||||
! $(GREP) -E "`printf '[ \\t]$$'`" cli/*.c cli/*.h cli/*.1 *.c *.h LICENSE Makefile build/cmake/CMakeLists.txt
|
||||
|
||||
.PHONY: lint-unicode
|
||||
lint-unicode:
|
||||
./tests/unicode_lint.sh
|
||||
|
||||
# =========================================================
|
||||
# make install is validated only for the following targets
|
||||
# =========================================================
|
||||
ifneq (,$(filter Linux Darwin GNU/kFreeBSD GNU Haiku OpenBSD FreeBSD NetBSD DragonFly SunOS CYGWIN% , $(UNAME)))
|
||||
|
||||
DESTDIR ?=
|
||||
# directory variables: GNU conventions prefer lowercase
|
||||
# see https://www.gnu.org/prep/standards/html_node/Makefile-Conventions.html
|
||||
# support both lower and uppercase (BSD), use uppercase in script
|
||||
prefix ?= /usr/local
|
||||
PREFIX ?= $(prefix)
|
||||
exec_prefix ?= $(PREFIX)
|
||||
EXEC_PREFIX ?= $(exec_prefix)
|
||||
libdir ?= $(EXEC_PREFIX)/lib
|
||||
LIBDIR ?= $(libdir)
|
||||
includedir ?= $(PREFIX)/include
|
||||
INCLUDEDIR ?= $(includedir)
|
||||
bindir ?= $(EXEC_PREFIX)/bin
|
||||
BINDIR ?= $(bindir)
|
||||
datarootdir ?= $(PREFIX)/share
|
||||
mandir ?= $(datarootdir)/man
|
||||
man1dir ?= $(mandir)/man1
|
||||
|
||||
ifneq (,$(filter $(UNAME),FreeBSD NetBSD DragonFly))
|
||||
PKGCONFIGDIR ?= $(PREFIX)/libdata/pkgconfig
|
||||
else
|
||||
PKGCONFIGDIR ?= $(LIBDIR)/pkgconfig
|
||||
endif
|
||||
|
||||
ifneq (,$(filter $(UNAME),OpenBSD NetBSD DragonFly SunOS))
|
||||
MANDIR ?= $(PREFIX)/man/man1
|
||||
else
|
||||
MANDIR ?= $(man1dir)
|
||||
endif
|
||||
|
||||
ifneq (,$(filter $(UNAME),SunOS))
|
||||
INSTALL ?= ginstall
|
||||
else
|
||||
INSTALL ?= install
|
||||
endif
|
||||
|
||||
INSTALL_PROGRAM ?= $(INSTALL)
|
||||
INSTALL_DATA ?= $(INSTALL) -m 644
|
||||
MAKE_DIR ?= $(INSTALL) -d -m 755
|
||||
|
||||
|
||||
# Escape special symbols by putting each character into its separate class
|
||||
EXEC_PREFIX_REGEX ?= $(shell echo "$(EXEC_PREFIX)" | $(SED) $(SED_ERE_OPT) -e "s/([^^])/[\1]/g" -e "s/\\^/\\\\^/g")
|
||||
PREFIX_REGEX ?= $(shell echo "$(PREFIX)" | $(SED) $(SED_ERE_OPT) -e "s/([^^])/[\1]/g" -e "s/\\^/\\\\^/g")
|
||||
|
||||
PCLIBDIR ?= $(shell echo "$(LIBDIR)" | $(SED) -n $(SED_ERE_OPT) -e "s@^$(EXEC_PREFIX_REGEX)(/|$$)@@p")
|
||||
PCINCDIR ?= $(shell echo "$(INCLUDEDIR)" | $(SED) -n $(SED_ERE_OPT) -e "s@^$(PREFIX_REGEX)(/|$$)@@p")
|
||||
PCEXECDIR?= $(if $(filter $(PREFIX),$(EXEC_PREFIX)),$$\{prefix\},$(EXEC_PREFIX))
|
||||
|
||||
ifeq (,$(PCLIBDIR))
|
||||
# Additional prefix check is required, since the empty string is technically a
|
||||
# valid PCLIBDIR
|
||||
ifeq (,$(shell echo "$(LIBDIR)" | $(SED) -n $(SED_ERE_OPT) -e "\\@^$(EXEC_PREFIX_REGEX)(/|$$)@ p"))
|
||||
$(error configured libdir ($(LIBDIR)) is outside of exec_prefix ($(EXEC_PREFIX)), can't generate pkg-config file)
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq (,$(PCINCDIR))
|
||||
# Additional prefix check is required, since the empty string is technically a
|
||||
# valid PCINCDIR
|
||||
ifeq (,$(shell echo "$(INCLUDEDIR)" | $(SED) -n $(SED_ERE_OPT) -e "\\@^$(PREFIX_REGEX)(/|$$)@ p"))
|
||||
$(error configured includedir ($(INCLUDEDIR)) is outside of prefix ($(PREFIX)), can't generate pkg-config file)
|
||||
endif
|
||||
endif
|
||||
|
||||
libxxhash.pc: libxxhash.pc.in
|
||||
@echo creating pkgconfig
|
||||
$(SED) $(SED_ERE_OPT) -e 's|@PREFIX@|$(PREFIX)|' \
|
||||
-e 's|@EXECPREFIX@|$(PCEXECDIR)|' \
|
||||
-e 's|@LIBDIR@|$$\{exec_prefix\}/$(PCLIBDIR)|' \
|
||||
-e 's|@INCLUDEDIR@|$$\{prefix\}/$(PCINCDIR)|' \
|
||||
-e 's|@VERSION@|$(LIBVER)|' \
|
||||
$< > $@
|
||||
|
||||
|
||||
install_libxxhash.a: libxxhash.a
|
||||
@echo Installing libxxhash.a
|
||||
$(MAKE_DIR) $(DESTDIR)$(LIBDIR)
|
||||
$(INSTALL_DATA) libxxhash.a $(DESTDIR)$(LIBDIR)
|
||||
|
||||
install_libxxhash: libxxhash
|
||||
@echo Installing libxxhash
|
||||
$(MAKE_DIR) $(DESTDIR)$(LIBDIR)
|
||||
$(INSTALL_PROGRAM) $(LIBXXH) $(DESTDIR)$(LIBDIR)
|
||||
ln -sf $(LIBXXH) $(DESTDIR)$(LIBDIR)/libxxhash.$(SHARED_EXT_MAJOR)
|
||||
ln -sf libxxhash.$(SHARED_EXT_MAJOR) $(DESTDIR)$(LIBDIR)/libxxhash.$(SHARED_EXT)
|
||||
|
||||
install_libxxhash.includes:
|
||||
$(INSTALL) -d -m 755 $(DESTDIR)$(INCLUDEDIR) # includes
|
||||
$(INSTALL_DATA) xxhash.h $(DESTDIR)$(INCLUDEDIR)
|
||||
$(INSTALL_DATA) xxh3.h $(DESTDIR)$(INCLUDEDIR) # for compatibility, will be removed in v0.9.0
|
||||
ifeq ($(LIBXXH_DISPATCH),1)
|
||||
$(INSTALL_DATA) xxh_x86dispatch.h $(DESTDIR)$(INCLUDEDIR)
|
||||
endif
|
||||
|
||||
install_libxxhash.pc: libxxhash.pc
|
||||
@echo Installing pkgconfig
|
||||
$(MAKE_DIR) $(DESTDIR)$(PKGCONFIGDIR)/
|
||||
$(INSTALL_DATA) libxxhash.pc $(DESTDIR)$(PKGCONFIGDIR)/
|
||||
|
||||
install_xxhsum: xxhsum
|
||||
@echo Installing xxhsum
|
||||
$(MAKE_DIR) $(DESTDIR)$(BINDIR)/
|
||||
$(INSTALL_PROGRAM) xxhsum$(EXT) $(DESTDIR)$(BINDIR)/xxhsum$(EXT)
|
||||
ln -sf xxhsum$(EXT) $(DESTDIR)$(BINDIR)/xxh32sum$(EXT)
|
||||
ln -sf xxhsum$(EXT) $(DESTDIR)$(BINDIR)/xxh64sum$(EXT)
|
||||
ln -sf xxhsum$(EXT) $(DESTDIR)$(BINDIR)/xxh128sum$(EXT)
|
||||
ln -sf xxhsum$(EXT) $(DESTDIR)$(BINDIR)/xxh3sum$(EXT)
|
||||
|
||||
install_man:
|
||||
@echo Installing man pages
|
||||
$(MAKE_DIR) $(DESTDIR)$(MANDIR)/
|
||||
$(INSTALL_DATA) $(MAN) $(DESTDIR)$(MANDIR)/xxhsum.1
|
||||
ln -sf xxhsum.1 $(DESTDIR)$(MANDIR)/xxh32sum.1
|
||||
ln -sf xxhsum.1 $(DESTDIR)$(MANDIR)/xxh64sum.1
|
||||
ln -sf xxhsum.1 $(DESTDIR)$(MANDIR)/xxh128sum.1
|
||||
ln -sf xxhsum.1 $(DESTDIR)$(MANDIR)/xxh3sum.1
|
||||
|
||||
.PHONY: install
|
||||
## install libraries, CLI, links and man pages
|
||||
install: install_libxxhash.a install_libxxhash install_libxxhash.includes install_libxxhash.pc install_xxhsum install_man
|
||||
@echo xxhash installation completed
|
||||
|
||||
.PHONY: uninstall
|
||||
uninstall: ## uninstall libraries, CLI, links and man page
|
||||
$(RM) $(DESTDIR)$(LIBDIR)/libxxhash.a
|
||||
$(RM) $(DESTDIR)$(LIBDIR)/libxxhash.$(SHARED_EXT)
|
||||
$(RM) $(DESTDIR)$(LIBDIR)/libxxhash.$(SHARED_EXT_MAJOR)
|
||||
$(RM) $(DESTDIR)$(LIBDIR)/$(LIBXXH)
|
||||
$(RM) $(DESTDIR)$(INCLUDEDIR)/xxhash.h
|
||||
$(RM) $(DESTDIR)$(INCLUDEDIR)/xxh3.h
|
||||
$(RM) $(DESTDIR)$(INCLUDEDIR)/xxh_x86dispatch.h
|
||||
$(RM) $(DESTDIR)$(PKGCONFIGDIR)/libxxhash.pc
|
||||
$(RM) $(DESTDIR)$(BINDIR)/xxh32sum
|
||||
$(RM) $(DESTDIR)$(BINDIR)/xxh64sum
|
||||
$(RM) $(DESTDIR)$(BINDIR)/xxh128sum
|
||||
$(RM) $(DESTDIR)$(BINDIR)/xxh3sum
|
||||
$(RM) $(DESTDIR)$(BINDIR)/xxhsum
|
||||
$(RM) $(DESTDIR)$(MANDIR)/xxh32sum.1
|
||||
$(RM) $(DESTDIR)$(MANDIR)/xxh64sum.1
|
||||
$(RM) $(DESTDIR)$(MANDIR)/xxh128sum.1
|
||||
$(RM) $(DESTDIR)$(MANDIR)/xxh3sum.1
|
||||
$(RM) $(DESTDIR)$(MANDIR)/xxhsum.1
|
||||
@echo xxhsum successfully uninstalled
|
||||
|
||||
endif
|
||||
71
deps/xxhash/build/make/README.md
vendored
Normal file
71
deps/xxhash/build/make/README.md
vendored
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# multiconf.make
|
||||
|
||||
**multiconf.make** is a self-contained Makefile include that lets you build the **same targets under many different flag sets**. For example debug vs release, ASan vs UBSan, GCC vs Clang.
|
||||
Each different set of flags generates object files into its own **dedicated cache directory**, so objects compiled with one configuration are never reused by another.
|
||||
Object files from previous configurations are preserved, so swapping back to a previous configuration only requires compiling objects which have actually changed.
|
||||
|
||||
---
|
||||
|
||||
## Benefits at a glance
|
||||
|
||||
| Benefit | What `multiconf.make` does |
|
||||
| --- | --- |
|
||||
| **Isolated configs** | Stores objects into `cachedObjs/<hash>/`, one directory per unique flag set. |
|
||||
| **Fast switching** | Reusing an old config is instant—link only, no recompilation. |
|
||||
| **Header deps** | Edits to headers trigger only needed rebuilds. |
|
||||
| **One-liner targets** | Macros (`c_program`, `cxx_program`, …) hide all rule boilerplate. |
|
||||
| **Parallel-ready** | Safe with `make -j`, no duplicate compiles of shared sources. |
|
||||
| **Controlled verbosity** | Default only lists objects, while `V=1` display full commands. |
|
||||
| **`clean` included** | `make clean` deletes all objects, binaries and links. |
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1 · List your sources
|
||||
|
||||
```make
|
||||
C_SRCDIRS := src src/cdeps # all .c are in these directories
|
||||
CXX_SRCDIRS := src src/cxxdeps # all .cpp are in these directories
|
||||
```
|
||||
|
||||
### 2 · Add and include
|
||||
|
||||
```make
|
||||
# root/Makefile
|
||||
include multiconf.make
|
||||
```
|
||||
|
||||
### 3 · Declare targets
|
||||
|
||||
```make
|
||||
app:
|
||||
$(eval $(call c_program,app,app.o cdeps/obj.o))
|
||||
|
||||
test:
|
||||
$(eval $(call cxx_program,test, test.o cxxdeps/objcxx.o))
|
||||
|
||||
lib.a:
|
||||
$(eval $(call static_library,lib.a, lib.o cdeps/obj.o))
|
||||
|
||||
lib.so:
|
||||
$(eval $(call c_dynamic_library,lib.so, lib.o cdeps/obj.o))
|
||||
```
|
||||
|
||||
### 4 · Build any config you like
|
||||
|
||||
```sh
|
||||
# Release with GCC
|
||||
make CFLAGS="-O3"
|
||||
|
||||
# Debug with Clang + AddressSanitizer (new cache dir)
|
||||
make CC=clang CFLAGS="-g -O0 -fsanitize=address"
|
||||
|
||||
# Switch back to GCC release (objects still valid, relink only)
|
||||
make CFLAGS="-O3"
|
||||
```
|
||||
|
||||
Objects for each command live in different sub-folders; nothing overlaps.
|
||||
|
||||
---
|
||||
|
||||
293
deps/xxhash/build/make/multiconf.make
vendored
Normal file
293
deps/xxhash/build/make/multiconf.make
vendored
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
# ##########################################################################
|
||||
# multiconf.make
|
||||
# Copyright (C) Yann Collet
|
||||
#
|
||||
# GPL v2 License
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# ##########################################################################
|
||||
|
||||
|
||||
# Provides c_program(_shared_o) and cxx_program(_shared_o) target generation macros
|
||||
# Provides static_library and c_dynamic_library target generation macros
|
||||
# Support recompilation of only impacted units when an associated *.h is updated.
|
||||
# Provides V=1 / VERBOSE=1 support. V=2 is used for debugging purposes.
|
||||
# Complement target clean: delete objects and binaries created by this script
|
||||
|
||||
# Requires:
|
||||
# - C_SRCDIRS, CXX_SRCDIRS, ASM_SRCDIRS defined
|
||||
# OR
|
||||
# C_SRCS, CXX_SRCS and ASM_SRCS variables defined
|
||||
# *and* vpath set to find all source files
|
||||
# OR
|
||||
# C_OBJS, CXX_OBJS and ASM_OBJS variables defined
|
||||
# *and* vpath set to find all source files
|
||||
# - directory `cachedObjs/` available to cache object files.
|
||||
# alternatively: set CACHE_ROOT to some different value.
|
||||
# Optional:
|
||||
# - HASH can be set to a different custom hash program.
|
||||
|
||||
# *_program*: generates a recipe for a target that will be built in a cache directory.
|
||||
# The cache directory is automatically derived from CACHE_ROOT and list of flags and compilers.
|
||||
# *_shared_o* variants are optional optimization variants, that share the same objects across multiple targets.
|
||||
# However, as a consequence, all these objects must have exactly the same list of flags,
|
||||
# which in practice means that there must be no target-level modification (like: target: CFLAGS += someFlag).
|
||||
# If unsure, only use the standard variants, c_program and cxx_program.
|
||||
|
||||
# All *_program* macro functions take up to 4 argument:
|
||||
# - The name of the target
|
||||
# - The list of object files to build in the cache directory
|
||||
# - An optional list of dependencies for linking, that will not be built
|
||||
# - An optional complementary recipe code, that will run after compilation and link
|
||||
|
||||
|
||||
# Silent mode is default; use V = 1 or VERBOSE = 1 to see compilation lines
|
||||
VERBOSE ?= $(V)
|
||||
$(VERBOSE).SILENT:
|
||||
|
||||
# Directory where object files will be built
|
||||
CACHE_ROOT ?= cachedObjs
|
||||
|
||||
# --------------------------------------------------------------------------------------------
|
||||
|
||||
# Dependency management
|
||||
DEPFLAGS = -MT $@ -MMD -MP -MF
|
||||
|
||||
# Include dependency files
|
||||
include $(wildcard $(CACHE_ROOT)/**/*.d)
|
||||
include $(wildcard $(CACHE_ROOT)/generic/*/*.d)
|
||||
|
||||
# --------------------------------------------------------------------------------------------
|
||||
|
||||
# Automatic determination of build artifacts cache directory, keyed on build
|
||||
# flags, so that we can do incremental, parallel builds of different binaries
|
||||
# with different build flags without collisions.
|
||||
|
||||
UNAME ?= $(shell uname)
|
||||
ifeq ($(UNAME), Darwin)
|
||||
HASH ?= md5
|
||||
else ifeq ($(UNAME), FreeBSD)
|
||||
HASH ?= gmd5sum
|
||||
else ifeq ($(UNAME), OpenBSD)
|
||||
HASH ?= md5
|
||||
endif
|
||||
HASH ?= md5sum
|
||||
|
||||
HAVE_HASH := $(shell echo 1 | $(HASH) > /dev/null && echo 1 || echo 0)
|
||||
ifeq ($(HAVE_HASH),0)
|
||||
$(info warning : could not find HASH ($(HASH)), required to differentiate builds using different flags)
|
||||
HASH_FUNC = generic/$(1)
|
||||
else
|
||||
HASH_FUNC = $(firstword $(shell echo $(2) | $(HASH) ))
|
||||
endif
|
||||
|
||||
|
||||
MKDIR ?= mkdir
|
||||
LN ?= ln
|
||||
|
||||
# --------------------------------------------------------------------------------------------
|
||||
# The following macros are used to create object files in the cache directory.
|
||||
# The object files are named after the source file, but with a different path.
|
||||
|
||||
# Create build directories on-demand.
|
||||
#
|
||||
# For some reason, make treats the directory as an intermediate file and tries
|
||||
# to delete it. So we work around that by marking it "precious". Solution found
|
||||
# here:
|
||||
# http://ismail.badawi.io/blog/2017/03/28/automatic-directory-creation-in-make/
|
||||
.PRECIOUS: $(CACHE_ROOT)/%/.
|
||||
$(CACHE_ROOT)/%/. :
|
||||
$(MKDIR) -p $@
|
||||
|
||||
|
||||
define addTargetAsmObject # targetName, addlDeps
|
||||
$$(if $$(filter 2,$$(V)),$$(info $$(call $(0),$(1),$(2))))
|
||||
|
||||
.PRECIOUS: $$(CACHE_ROOT)/%/$(1)
|
||||
$$(CACHE_ROOT)/%/$(1) : $(1:.o=.S) $(2) | $$(CACHE_ROOT)/%/.
|
||||
@echo AS $$@
|
||||
$$(CC) $$(CPPFLAGS) $$(CXXFLAGS) $$(DEPFLAGS) $$(CACHE_ROOT)/$$*/$(1:.o=.d) -c $$< -o $$@
|
||||
|
||||
endef # addTargetAsmObject
|
||||
|
||||
define addTargetCObject # targetName, addlDeps
|
||||
$$(if $$(filter 2,$$(V)),$$(info $$(call $(0),$(1),$(2)))) #debug print
|
||||
|
||||
.PRECIOUS: $$(CACHE_ROOT)/%/$(1)
|
||||
$$(CACHE_ROOT)/%/$(1) : $(1:.o=.c) $(2) | $$(CACHE_ROOT)/%/.
|
||||
@echo CC $$@
|
||||
$$(CC) $$(CPPFLAGS) $$(CFLAGS) $$(DEPFLAGS) $$(CACHE_ROOT)/$$*/$(1:.o=.d) -c $$< -o $$@
|
||||
|
||||
endef # addTargetCObject
|
||||
|
||||
define addTargetCxxObject # targetName, suffix, addlDeps
|
||||
$$(if $$(filter 2,$$(V)),$$(info $$(call $(0),$(1),$(2),$(3))))
|
||||
|
||||
.PRECIOUS: $$(CACHE_ROOT)/%/$(1)
|
||||
$$(CACHE_ROOT)/%/$(1) : $(1:.o=.$(2)) $(3) | $$(CACHE_ROOT)/%/.
|
||||
@echo CXX $$@
|
||||
$$(CXX) $$(CPPFLAGS) $$(CXXFLAGS) $$(DEPFLAGS) $$(CACHE_ROOT)/$$*/$(1:.o=.d) -c $$< -o $$@
|
||||
|
||||
endef # addTargetCxxObject
|
||||
|
||||
# Create targets for individual object files
|
||||
C_SRCDIRS += .
|
||||
vpath %.c $(C_SRCDIRS)
|
||||
CXX_SRCDIRS += .
|
||||
vpath %.cpp $(CXX_SRCDIRS)
|
||||
vpath %.cc $(CXX_SRCDIRS)
|
||||
ASM_SRCDIRS += .
|
||||
vpath %.S $(ASM_SRCDIRS)
|
||||
|
||||
# If C_SRCDIRS, CXX_SRCDIRS and ASM_SRCDIRS are not defined, use C_SRCS, CXX_SRCS and ASM_SRCS
|
||||
C_SRCS ?= $(notdir $(foreach dir,$(C_SRCDIRS),$(wildcard $(dir)/*.c)))
|
||||
CPP_SRCS ?= $(notdir $(foreach dir,$(CXX_SRCDIRS),$(wildcard $(dir)/*.cpp)))
|
||||
CC_SRCS ?= $(notdir $(foreach dir,$(CXX_SRCDIRS),$(wildcard $(dir)/*.cc)))
|
||||
CXX_SRCS ?= $(CPP_SRCS) $(CC_SRCS)
|
||||
ASM_SRCS ?= $(notdir $(foreach dir,$(ASM_SRCDIRS),$(wildcard $(dir)/*.S)))
|
||||
|
||||
# If C_SRCS, CXX_SRCS and ASM_SRCS are not defined, use C_OBJS, CXX_OBJS and ASM_OBJS
|
||||
C_OBJS ?= $(patsubst %.c,%.o,$(C_SRCS))
|
||||
CPP_OBJS ?= $(patsubst %.cpp,%.o,$(CPP_SRCS))
|
||||
CC_OBJS ?= $(patsubst %.cc,%.o,$(CC_SRCS))
|
||||
CXX_OBJS ?= $(CPP_OBJS) $(CC_OBJS) # Note: not used
|
||||
ASM_OBJS ?= $(patsubst %.S,%.o,$(ASM_SRCS))
|
||||
|
||||
$(foreach OBJ,$(C_OBJS),$(eval $(call addTargetCObject,$(OBJ))))
|
||||
$(foreach OBJ,$(CPP_OBJS),$(eval $(call addTargetCxxObject,$(OBJ),cpp)))
|
||||
$(foreach OBJ,$(CC_OBJS),$(eval $(call addTargetCxxObject,$(OBJ),cc)))
|
||||
$(foreach OBJ,$(ASM_OBJS),$(eval $(call addTargetAsmObject,$(OBJ))))
|
||||
|
||||
# --------------------------------------------------------------------------------------------
|
||||
# The following macros are used to create targets in the user Makefile.
|
||||
# Binaries are built in the cache directory, and then symlinked to the current directory.
|
||||
# The cache directory is automatically derived from CACHE_ROOT and list of flags and compilers.
|
||||
|
||||
|
||||
# static_library - Create build rules for a static library with caching
|
||||
# Parameters:
|
||||
# 1. libName - Library name (becomes output file and phony target)
|
||||
# 2. objectDeps - Object file dependencies (will be built in cache path)
|
||||
# The following parameters are all optional:
|
||||
# 3. extraDeps - Additional dependencies (no cache path prefix)
|
||||
# 4. postBuildCmds - Extra commands to run after AR
|
||||
# 5. extraHash - Additional key to compute the unique cache path
|
||||
# Example:
|
||||
# $(call static_library,libmath.a,vector.o matrix.o,$(CONFIG_H),strip $@,$(VERSION))
|
||||
define static_library # libName, objectDeps, extraDeps, postBuildCmds, extraHash
|
||||
|
||||
$$(if $$(filter 2,$$(V)),$$(info $$(call $(0),$(1),$(2),$(3),$(4),$(5))))
|
||||
MCM_ALL_BINS += $(1)
|
||||
|
||||
$$(CACHE_ROOT)/%/$(1) : $$(addprefix $$(CACHE_ROOT)/%/,$(2)) $(3)
|
||||
@echo AR $$@
|
||||
$$(AR) $$(ARFLAGS) $$@ $$^
|
||||
$(4)
|
||||
|
||||
.PHONY: $(1)
|
||||
$(1) : ARFLAGS = rcs
|
||||
$(1) : $$(CACHE_ROOT)/$$(call HASH_FUNC,$(1),$(2) $$(CPPFLAGS) $$(CC) $$(CFLAGS) $$(CXX) $$(CXXFLAGS) $$(AR) $$(ARFLAGS) $(5))/$(1)
|
||||
$$(LN) -sf $$< $$@
|
||||
|
||||
endef # static_library
|
||||
|
||||
|
||||
# c_dynamic_library - Create build rules for a C dynamic/shared library with caching
|
||||
# Parameters:
|
||||
# 1. libName - Library name (becomes output file and phony target)
|
||||
# 2. objectDeps - Object file dependencies (will be built in cache path)
|
||||
# The following parameters are all optional:
|
||||
# 3. extraDeps - Additional dependencies (no cache path prefix)
|
||||
# 4. postLinkCmds - Extra commands to run after linking
|
||||
# 5. extraHash - Additional key to compute the unique cache path
|
||||
# Example:
|
||||
# $(call c_dynamic_library,libmath.so,vector.o matrix.o,$(CONFIG_H),strip $@,$(VERSION))
|
||||
define c_dynamic_library # libName, objectDeps, extraDeps, postLinkCmds, extraHash
|
||||
|
||||
$$(if $$(filter 2,$$(V)),$$(info $$(call $(0),$(1),$(2),$(3),$(4),$(5))))
|
||||
MCM_ALL_BINS += $(1)
|
||||
|
||||
$$(CACHE_ROOT)/%/$(1) : $$(addprefix $$(CACHE_ROOT)/%/,$(2)) $(3)
|
||||
@echo LD $$@
|
||||
$$(CC) $$(CPPFLAGS) $$(CFLAGS) $$(LDFLAGS) -shared -o $$@ $$^ $$(LDLIBS)
|
||||
$(4)
|
||||
|
||||
.PHONY: $(1)
|
||||
$(1) : CFLAGS += -fPIC
|
||||
$(1) : $$(CACHE_ROOT)/$$(call HASH_FUNC,$(1),$(2) $$(CPPFLAGS) $$(CC) $$(CFLAGS) $$(LDFLAGS) $$(LDLIBS) $(5))/$(1)
|
||||
$$(LN) -sf $$< $$@
|
||||
|
||||
endef # c_dynamic_library
|
||||
|
||||
|
||||
# program_base - Create build rules for an executable program with caching
|
||||
# Parameters:
|
||||
# 1. progName - Executable name (becomes output file and phony target)
|
||||
# 2. objectDeps - Object file dependencies (will be prefixed with cache path)
|
||||
# Parameters 3 to 5 are optional:
|
||||
# 3. extraDeps - Additional dependencies (without cache path prefix)
|
||||
# 4. postLinkCmds - Extra commands to run after linking
|
||||
# 5. extraHash - Additional data to include in cache path hash
|
||||
# Parameters 6 & 7 are compulsory:
|
||||
# 6. compiler - Variable name of compiler to use (CC or CXX)
|
||||
# 7. compilerFlags - Variable name of compiler flags to use (CFLAGS or CXXFLAGS)
|
||||
# Example:
|
||||
# $(call program_base,myapp,main.o utils.o,$(CONFIG_H),strip $@,$(VERSION),CC,CFLAGS)
|
||||
# $(call program_base,mycppapp,main.o utils.o,$(CONFIG_H),strip $@,$(VERSION),CXX,CXXFLAGS)
|
||||
define program_base # progName, objectDeps, extraDeps, postLinkCmds, extraHash, compiler, compilerFlags
|
||||
|
||||
$$(if $$(filter 2,$$(V)),$$(info $$(call $(0),$(1),$(2),$(3),$(4),$(5),$(6),$(7))))
|
||||
MCM_ALL_BINS += $(1)
|
||||
|
||||
$$(CACHE_ROOT)/%/$(1) : $$(addprefix $$(CACHE_ROOT)/%/,$(2)) $(3)
|
||||
@echo LD $$@
|
||||
$$($(6)) $$(CPPFLAGS) $$($(7)) $$^ -o $$@ $$(LDFLAGS) $$(LDLIBS)
|
||||
$(4)
|
||||
|
||||
.PHONY: $(1)
|
||||
$(1) : $$(CACHE_ROOT)/$$(call HASH_FUNC,$(1),$$($(6)) $$(CPPFLAGS) $$($(7)) $$(LDFLAGS) $$(LDLIBS) $(5))/$(1)
|
||||
$$(LN) -sf $$< $$@$(EXT)
|
||||
|
||||
endef # program_base
|
||||
# Note: $(EXT) must be set to .exe for Windows
|
||||
|
||||
define c_program # progName, objectDeps, extraDeps, postLinkCmds
|
||||
$$(eval $$(call program_base,$(1),$(2),$(3),$(4),$(1)$(2),CC,CFLAGS))
|
||||
endef # c_program
|
||||
|
||||
define c_program_shared_o # progName, objectDeps, extraDeps, postLinkCmds
|
||||
$$(eval $$(call program_base,$(1),$(2),$(3),$(4),,CC,CFLAGS))
|
||||
endef # c_program_shared_o
|
||||
|
||||
define cxx_program # progName, objectDeps, extraDeps, postLinkCmds
|
||||
$$(eval $$(call program_base,$(1),$(2),$(3),$(4),$(1)$(2),CXX,CXXFLAGS))
|
||||
endef # cxx_program
|
||||
|
||||
define cxx_program_shared_o # progName, objectDeps, extraDeps, postLinkCmds
|
||||
$$(eval $$(call program_base,$(1),$(2),$(3),$(4),,CXX,CXXFLAGS))
|
||||
endef # cxx_program_shared_o
|
||||
|
||||
# --------------------------------------------------------------------------------------------
|
||||
|
||||
# Cleaning: delete all objects and binaries created by this script
|
||||
.PHONY: clean_cache
|
||||
clean_cache:
|
||||
$(RM) -rf $(CACHE_ROOT)
|
||||
$(RM) $(MCM_ALL_BINS)
|
||||
|
||||
# automatically attach to standard clean target
|
||||
.PHONY: clean
|
||||
clean: clean_cache
|
||||
42
deps/xxhash/xxhash.c
vendored
Normal file
42
deps/xxhash/xxhash.c
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* xxHash - Extremely Fast Hash algorithm
|
||||
* Copyright (C) 2012-2023 Yann Collet
|
||||
*
|
||||
* BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* You can contact the author at:
|
||||
* - xxHash homepage: https://www.xxhash.com
|
||||
* - xxHash source repository: https://github.com/Cyan4973/xxHash
|
||||
*/
|
||||
|
||||
/*
|
||||
* xxhash.c instantiates functions defined in xxhash.h
|
||||
*/
|
||||
|
||||
#define XXH_STATIC_LINKING_ONLY /* access advanced declarations */
|
||||
#define XXH_IMPLEMENTATION /* access definitions */
|
||||
|
||||
#include "xxhash.h"
|
||||
7488
deps/xxhash/xxhash.h
vendored
Normal file
7488
deps/xxhash/xxhash.h
vendored
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -35,7 +35,7 @@ endif
|
|||
ifneq ($(OPTIMIZATION),-O0)
|
||||
OPTIMIZATION+=-fno-omit-frame-pointer
|
||||
endif
|
||||
DEPENDENCY_TARGETS=hiredis linenoise lua hdr_histogram fpconv fast_float
|
||||
DEPENDENCY_TARGETS=hiredis linenoise lua hdr_histogram fpconv fast_float xxhash
|
||||
NODEPS:=clean distclean
|
||||
|
||||
# Default settings
|
||||
|
|
@ -257,7 +257,7 @@ ifdef OPENSSL_PREFIX
|
|||
endif
|
||||
|
||||
# Include paths to dependencies
|
||||
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src -I../deps/hdr_histogram -I../deps/fpconv -I../deps/fast_float
|
||||
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src -I../deps/hdr_histogram -I../deps/fpconv -I../deps/fast_float -I../deps/xxhash
|
||||
|
||||
# Determine systemd support and/or build preference (defaulting to auto-detection)
|
||||
BUILD_WITH_SYSTEMD=no
|
||||
|
|
@ -442,7 +442,7 @@ endif
|
|||
|
||||
# redis-server
|
||||
$(REDIS_SERVER_NAME): $(REDIS_SERVER_OBJ) $(REDIS_VEC_SETS_OBJ)
|
||||
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/lua/src/liblua.a ../deps/hdr_histogram/libhdrhistogram.a ../deps/fpconv/libfpconv.a ../deps/fast_float/libfast_float.a $(FINAL_LIBS)
|
||||
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/lua/src/liblua.a ../deps/hdr_histogram/libhdrhistogram.a ../deps/fpconv/libfpconv.a ../deps/fast_float/libfast_float.a ../deps/xxhash/libxxhash.a $(FINAL_LIBS)
|
||||
|
||||
# redis-sentinel
|
||||
$(REDIS_SENTINEL_NAME): $(REDIS_SERVER_NAME)
|
||||
|
|
|
|||
|
|
@ -10628,6 +10628,63 @@ struct COMMAND_ARG DECRBY_Args[] = {
|
|||
{MAKE_ARG("decrement",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
};
|
||||
|
||||
/********** DELEX ********************/
|
||||
|
||||
#ifndef SKIP_CMD_HISTORY_TABLE
|
||||
/* DELEX history */
|
||||
#define DELEX_History NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_TIPS_TABLE
|
||||
/* DELEX tips */
|
||||
#define DELEX_Tips NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
||||
/* DELEX key specs */
|
||||
keySpec DELEX_Keyspecs[1] = {
|
||||
{NULL,CMD_KEY_RM|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
|
||||
};
|
||||
#endif
|
||||
|
||||
/* DELEX condition argument table */
|
||||
struct COMMAND_ARG DELEX_condition_Subargs[] = {
|
||||
{MAKE_ARG("ifeq-value",ARG_TYPE_STRING,-1,"IFEQ",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("ifne-value",ARG_TYPE_STRING,-1,"IFNE",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("ifdeq-digest",ARG_TYPE_INTEGER,-1,"IFDEQ",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("ifdne-digest",ARG_TYPE_INTEGER,-1,"IFDNE",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
};
|
||||
|
||||
/* DELEX argument table */
|
||||
struct COMMAND_ARG DELEX_Args[] = {
|
||||
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("condition",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,4,NULL),.subargs=DELEX_condition_Subargs},
|
||||
};
|
||||
|
||||
/********** DIGEST ********************/
|
||||
|
||||
#ifndef SKIP_CMD_HISTORY_TABLE
|
||||
/* DIGEST history */
|
||||
#define DIGEST_History NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_TIPS_TABLE
|
||||
/* DIGEST tips */
|
||||
#define DIGEST_Tips NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
||||
/* DIGEST key specs */
|
||||
keySpec DIGEST_Keyspecs[1] = {
|
||||
{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
|
||||
};
|
||||
#endif
|
||||
|
||||
/* DIGEST argument table */
|
||||
struct COMMAND_ARG DIGEST_Args[] = {
|
||||
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
};
|
||||
|
||||
/********** GET ********************/
|
||||
|
||||
#ifndef SKIP_CMD_HISTORY_TABLE
|
||||
|
|
@ -10988,6 +11045,7 @@ commandHistory SET_History[] = {
|
|||
{"6.0.0","Added the `KEEPTTL` option."},
|
||||
{"6.2.0","Added the `GET`, `EXAT` and `PXAT` option."},
|
||||
{"7.0.0","Allowed the `NX` and `GET` options to be used together."},
|
||||
{"8.4.0","Added 'IFEQ', 'IFNE', 'IFDEQ', 'IFDNE' options."},
|
||||
};
|
||||
#endif
|
||||
|
||||
|
|
@ -11007,6 +11065,10 @@ keySpec SET_Keyspecs[1] = {
|
|||
struct COMMAND_ARG SET_condition_Subargs[] = {
|
||||
{MAKE_ARG("nx",ARG_TYPE_PURE_TOKEN,-1,"NX",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("xx",ARG_TYPE_PURE_TOKEN,-1,"XX",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("ifeq-value",ARG_TYPE_STRING,-1,"IFEQ",NULL,"8.4.0",CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("ifne-value",ARG_TYPE_STRING,-1,"IFNE",NULL,"8.4.0",CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("ifdeq-digest",ARG_TYPE_INTEGER,-1,"IFDEQ",NULL,"8.4.0",CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("ifdne-digest",ARG_TYPE_INTEGER,-1,"IFDNE",NULL,"8.4.0",CMD_ARG_NONE,0,NULL)},
|
||||
};
|
||||
|
||||
/* SET expiration argument table */
|
||||
|
|
@ -11022,7 +11084,7 @@ struct COMMAND_ARG SET_expiration_Subargs[] = {
|
|||
struct COMMAND_ARG SET_Args[] = {
|
||||
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("condition",ARG_TYPE_ONEOF,-1,NULL,NULL,"2.6.12",CMD_ARG_OPTIONAL,2,NULL),.subargs=SET_condition_Subargs},
|
||||
{MAKE_ARG("condition",ARG_TYPE_ONEOF,-1,NULL,NULL,"2.6.12",CMD_ARG_OPTIONAL,6,NULL),.subargs=SET_condition_Subargs},
|
||||
{MAKE_ARG("get",ARG_TYPE_PURE_TOKEN,-1,"GET",NULL,"6.2.0",CMD_ARG_OPTIONAL,0,NULL)},
|
||||
{MAKE_ARG("expiration",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,5,NULL),.subargs=SET_expiration_Subargs},
|
||||
};
|
||||
|
|
@ -11496,6 +11558,8 @@ struct COMMAND_STRUCT redisCommandTable[] = {
|
|||
{MAKE_CMD("append","Appends a string to the value of a key. Creates the key if it doesn't exist.","O(1). The amortized time complexity is O(1) assuming the appended value is small and the already present value is of any size, since the dynamic string library used by Redis will double the free space available on every reallocation.","2.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,APPEND_History,0,APPEND_Tips,0,appendCommand,3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_STRING,APPEND_Keyspecs,1,NULL,2),.args=APPEND_Args},
|
||||
{MAKE_CMD("decr","Decrements the integer value of a key by one. Uses 0 as initial value if the key doesn't exist.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,DECR_History,0,DECR_Tips,0,decrCommand,2,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_STRING,DECR_Keyspecs,1,NULL,1),.args=DECR_Args},
|
||||
{MAKE_CMD("decrby","Decrements a number from the integer value of a key. Uses 0 as initial value if the key doesn't exist.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,DECRBY_History,0,DECRBY_Tips,0,decrbyCommand,3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_STRING,DECRBY_Keyspecs,1,NULL,2),.args=DECRBY_Args},
|
||||
{MAKE_CMD("delex","Conditionally removes the specified key based on value or digest comparison.","O(1) for IFEQ/IFNE, O(N) for IFDEQ/IFDNE where N is the length of the string value.","8.4.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,DELEX_History,0,DELEX_Tips,0,delexCommand,-2,CMD_WRITE|CMD_FAST,ACL_CATEGORY_STRING,DELEX_Keyspecs,1,NULL,2),.args=DELEX_Args},
|
||||
{MAKE_CMD("digest","Returns the XXH3 hash of a string value.","O(N) where N is the length of the string value.","8.4.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,DIGEST_History,0,DIGEST_Tips,0,digestCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_STRING,DIGEST_Keyspecs,1,NULL,1),.args=DIGEST_Args},
|
||||
{MAKE_CMD("get","Returns the string value of a key.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,GET_History,0,GET_Tips,0,getCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_STRING,GET_Keyspecs,1,NULL,1),.args=GET_Args},
|
||||
{MAKE_CMD("getdel","Returns the string value of a key after deleting the key.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,GETDEL_History,0,GETDEL_Tips,0,getdelCommand,2,CMD_WRITE|CMD_FAST,ACL_CATEGORY_STRING,GETDEL_Keyspecs,1,NULL,1),.args=GETDEL_Args},
|
||||
{MAKE_CMD("getex","Returns the string value of a key after setting its expiration time.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,GETEX_History,0,GETEX_Tips,0,getexCommand,-2,CMD_WRITE|CMD_FAST,ACL_CATEGORY_STRING,GETEX_Keyspecs,1,NULL,2),.args=GETEX_Args},
|
||||
|
|
@ -11509,7 +11573,7 @@ struct COMMAND_STRUCT redisCommandTable[] = {
|
|||
{MAKE_CMD("mset","Atomically creates or modifies the string values of one or more keys.","O(N) where N is the number of keys to set.","1.0.1",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,MSET_History,0,MSET_Tips,2,msetCommand,-3,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_STRING,MSET_Keyspecs,1,NULL,1),.args=MSET_Args},
|
||||
{MAKE_CMD("msetnx","Atomically modifies the string values of one or more keys only when all keys don't exist.","O(N) where N is the number of keys to set.","1.0.1",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,MSETNX_History,0,MSETNX_Tips,0,msetnxCommand,-3,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_STRING,MSETNX_Keyspecs,1,NULL,1),.args=MSETNX_Args},
|
||||
{MAKE_CMD("psetex","Sets both string value and expiration time in milliseconds of a key. The key is created if it doesn't exist.","O(1)","2.6.0",CMD_DOC_DEPRECATED,"`SET` with the `PX` argument","2.6.12","string",COMMAND_GROUP_STRING,PSETEX_History,0,PSETEX_Tips,0,psetexCommand,4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_STRING,PSETEX_Keyspecs,1,NULL,3),.args=PSETEX_Args},
|
||||
{MAKE_CMD("set","Sets the string value of a key, ignoring its type. The key is created if it doesn't exist.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,SET_History,4,SET_Tips,0,setCommand,-3,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_STRING,SET_Keyspecs,1,setGetKeys,5),.args=SET_Args},
|
||||
{MAKE_CMD("set","Sets the string value of a key, ignoring its type. The key is created if it doesn't exist.","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,SET_History,5,SET_Tips,0,setCommand,-3,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_STRING,SET_Keyspecs,1,setGetKeys,5),.args=SET_Args},
|
||||
{MAKE_CMD("setex","Sets the string value and expiration time of a key. Creates the key if it doesn't exist.","O(1)","2.0.0",CMD_DOC_DEPRECATED,"`SET` with the `EX` argument","2.6.12","string",COMMAND_GROUP_STRING,SETEX_History,0,SETEX_Tips,0,setexCommand,4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_STRING,SETEX_Keyspecs,1,NULL,3),.args=SETEX_Args},
|
||||
{MAKE_CMD("setnx","Set the string value of a key only when the key doesn't exist.","O(1)","1.0.0",CMD_DOC_DEPRECATED,"`SET` with the `NX` argument","2.6.12","string",COMMAND_GROUP_STRING,SETNX_History,0,SETNX_Tips,0,setnxCommand,3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_STRING,SETNX_Keyspecs,1,NULL,2),.args=SETNX_Args},
|
||||
{MAKE_CMD("setrange","Overwrites a part of a string value with another by an offset. Creates the key if it doesn't exist.","O(1), not counting the time taken to copy the new string in place. Usually, this string is very small so the amortized complexity is O(1). Otherwise, complexity is O(M) with M being the length of the value argument.","2.2.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,SETRANGE_History,0,SETRANGE_Tips,0,setrangeCommand,4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_STRING,SETRANGE_Keyspecs,1,NULL,3),.args=SETRANGE_Args},
|
||||
|
|
|
|||
87
src/commands/delex.json
Normal file
87
src/commands/delex.json
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
{
|
||||
"DELEX": {
|
||||
"summary": "Conditionally removes the specified key based on value or digest comparison.",
|
||||
"complexity": "O(1) for IFEQ/IFNE, O(N) for IFDEQ/IFDNE where N is the length of the string value.",
|
||||
"group": "string",
|
||||
"since": "8.4.0",
|
||||
"arity": -2,
|
||||
"function": "delexCommand",
|
||||
"command_flags": [
|
||||
"WRITE",
|
||||
"FAST"
|
||||
],
|
||||
"acl_categories": [
|
||||
"STRING"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"RM",
|
||||
"DELETE"
|
||||
],
|
||||
"begin_search": {
|
||||
"index": {
|
||||
"pos": 1
|
||||
}
|
||||
},
|
||||
"find_keys": {
|
||||
"range": {
|
||||
"lastkey": 0,
|
||||
"step": 1,
|
||||
"limit": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "The key exists but holds a non-string value",
|
||||
"const": "WRONGTYPE"
|
||||
},
|
||||
{
|
||||
"description": "The key does not exist or the specified condition was not met.",
|
||||
"const": 0
|
||||
},
|
||||
{
|
||||
"description": "The key was deleted.",
|
||||
"const": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key",
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "condition",
|
||||
"type": "oneof",
|
||||
"optional": true,
|
||||
"arguments": [
|
||||
{
|
||||
"name": "ifeq-value",
|
||||
"type": "string",
|
||||
"token": "IFEQ"
|
||||
},
|
||||
{
|
||||
"name": "ifne-value",
|
||||
"type": "string",
|
||||
"token": "IFNE"
|
||||
},
|
||||
{
|
||||
"name": "ifdeq-digest",
|
||||
"type": "integer",
|
||||
"token": "IFDEQ"
|
||||
},
|
||||
{
|
||||
"name": "ifdne-digest",
|
||||
"type": "integer",
|
||||
"token": "IFDNE"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
56
src/commands/digest.json
Normal file
56
src/commands/digest.json
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
{
|
||||
"DIGEST": {
|
||||
"summary": "Returns the XXH3 hash of a string value.",
|
||||
"complexity": "O(N) where N is the length of the string value.",
|
||||
"group": "string",
|
||||
"since": "8.4.0",
|
||||
"arity": 2,
|
||||
"function": "digestCommand",
|
||||
"command_flags": [
|
||||
"READONLY",
|
||||
"FAST"
|
||||
],
|
||||
"acl_categories": [
|
||||
"STRING"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"RO",
|
||||
"ACCESS"
|
||||
],
|
||||
"begin_search": {
|
||||
"index": {
|
||||
"pos": 1
|
||||
}
|
||||
},
|
||||
"find_keys": {
|
||||
"range": {
|
||||
"lastkey": 0,
|
||||
"step": 1,
|
||||
"limit": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "The XXH3 64-bit hash of the string value as a signed integer.",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "Key does not exist",
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key",
|
||||
"key_spec_index": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,10 @@
|
|||
[
|
||||
"7.0.0",
|
||||
"Allowed the `NX` and `GET` options to be used together."
|
||||
],
|
||||
[
|
||||
"8.4.0",
|
||||
"Added 'IFEQ', 'IFNE', 'IFDEQ', 'IFDNE' options."
|
||||
]
|
||||
],
|
||||
"command_flags": [
|
||||
|
|
@ -58,7 +62,7 @@
|
|||
"reply_schema": {
|
||||
"anyOf":[
|
||||
{
|
||||
"description": "`GET` not given: Operation was aborted (conflict with one of the `XX`/`NX` options).",
|
||||
"description": "`GET` not given: Operation was aborted (conflict with one of the `XX`/`NX`/`IFEQ`/`IFDEQ` options or condition not met).",
|
||||
"type": "null"
|
||||
},
|
||||
{
|
||||
|
|
@ -100,6 +104,30 @@
|
|||
"name": "xx",
|
||||
"type": "pure-token",
|
||||
"token": "XX"
|
||||
},
|
||||
{
|
||||
"name": "ifeq-value",
|
||||
"type": "string",
|
||||
"token": "IFEQ",
|
||||
"since": "8.4.0"
|
||||
},
|
||||
{
|
||||
"name": "ifne-value",
|
||||
"type": "string",
|
||||
"token": "IFNE",
|
||||
"since": "8.4.0"
|
||||
},
|
||||
{
|
||||
"name": "ifdeq-digest",
|
||||
"type": "integer",
|
||||
"token": "IFDEQ",
|
||||
"since": "8.4.0"
|
||||
},
|
||||
{
|
||||
"name": "ifdne-digest",
|
||||
"type": "integer",
|
||||
"token": "IFDNE",
|
||||
"since": "8.4.0"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
88
src/db.c
88
src/db.c
|
|
@ -1198,6 +1198,94 @@ void delCommand(client *c) {
|
|||
delGenericCommand(c,server.lazyfree_lazy_user_del);
|
||||
}
|
||||
|
||||
/* DELEX key [IFEQ match-value|IFNE match-value|IFDEQ match-digest|IFDNE match-digest]
|
||||
*
|
||||
* Conditionally removes the specified key. A key is ignored if it does not
|
||||
* exist.
|
||||
* If no condition is specified the behavior is the same as DEL command.
|
||||
* If condition is specified the key must be of STRING type.
|
||||
*
|
||||
* IFEQ/IFNE conditions check the match-value against the value of the key
|
||||
* IFDEQ/IFDNE conditions check the match-digest against the digest of the key's value.*/
|
||||
void delexCommand(client *c) {
|
||||
kvobj *o;
|
||||
int deleted = 0, should_delete = 0;
|
||||
|
||||
robj *key = c->argv[1];
|
||||
o = lookupKeyRead(c->db, key);
|
||||
if (o == NULL) {
|
||||
addReplyLongLong(c, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
/* If there are no conditions specified we just delete the key */
|
||||
if (c->argc == 2) {
|
||||
delGenericCommand(c, server.lazyfree_lazy_server_del);
|
||||
return;
|
||||
}
|
||||
|
||||
/* If any conditions are specified the only supported key type for now is
|
||||
* string */
|
||||
if (o->type != OBJ_STRING) {
|
||||
addReplyError(c, "Key should be of string type if conditions are specified");
|
||||
return;
|
||||
}
|
||||
|
||||
/* If we have more than two arguments the next two are condition and
|
||||
* match-value */
|
||||
if (c->argc != 4) {
|
||||
addReplyErrorArity(c);
|
||||
return;
|
||||
}
|
||||
|
||||
char *condition = c->argv[2]->ptr;
|
||||
if (!strcasecmp("ifeq", condition)) {
|
||||
robj *valueobj = getDecodedObject(o);
|
||||
sds match_value = c->argv[3]->ptr;
|
||||
if (sdscmp(valueobj->ptr, match_value) == 0)
|
||||
should_delete = 1;
|
||||
|
||||
decrRefCount(valueobj);
|
||||
} else if (!strcasecmp("ifne", condition)) {
|
||||
robj *valueobj = getDecodedObject(o);
|
||||
sds match_value = c->argv[3]->ptr;
|
||||
if (sdscmp(valueobj->ptr, match_value) != 0)
|
||||
should_delete = 1;
|
||||
|
||||
decrRefCount(valueobj);
|
||||
} else if (!strcasecmp("ifdeq", condition)) {
|
||||
sds current_digest = stringDigest(o);
|
||||
if (sdscmp(current_digest, c->argv[3]->ptr) == 0)
|
||||
should_delete = 1;
|
||||
|
||||
sdsfree(current_digest);
|
||||
} else if (!strcasecmp("ifdne", condition)) {
|
||||
sds current_digest = stringDigest(o);
|
||||
if (sdscmp(current_digest, c->argv[3]->ptr) != 0)
|
||||
should_delete = 1;
|
||||
|
||||
sdsfree(current_digest);
|
||||
} else {
|
||||
addReplyError(c, "Invalid condition. Use IFEQ, IFNE, IFDEQ, or IFDNE");
|
||||
return;
|
||||
}
|
||||
|
||||
if (should_delete) {
|
||||
deleted = server.lazyfree_lazy_server_del ?
|
||||
dbAsyncDelete(c->db, key) :
|
||||
dbSyncDelete(c->db, key);
|
||||
}
|
||||
|
||||
if (deleted) {
|
||||
rewriteClientCommandVector(c, 2, shared.del, key);
|
||||
signalModifiedKey(c, c->db, key);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC, "del", key, c->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
addReplyLongLong(c, deleted);
|
||||
}
|
||||
|
||||
void unlinkCommand(client *c) {
|
||||
delGenericCommand(c,1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3881,6 +3881,9 @@ uint64_t redisBuildId(void);
|
|||
const char *redisBuildIdRaw(void);
|
||||
char *redisBuildIdString(void);
|
||||
|
||||
/* XXH3 hash of a string as hex string */
|
||||
sds stringDigest(robj *o);
|
||||
|
||||
/* Commands prototypes */
|
||||
void authCommand(client *c);
|
||||
void pingCommand(client *c);
|
||||
|
|
@ -3901,6 +3904,7 @@ void getCommand(client *c);
|
|||
void getexCommand(client *c);
|
||||
void getdelCommand(client *c);
|
||||
void delCommand(client *c);
|
||||
void delexCommand(client *c);
|
||||
void unlinkCommand(client *c);
|
||||
void existsCommand(client *c);
|
||||
void setbitCommand(client *c);
|
||||
|
|
@ -4156,6 +4160,7 @@ void lcsCommand(client *c);
|
|||
void quitCommand(client *c);
|
||||
void resetCommand(client *c);
|
||||
void failoverCommand(client *c);
|
||||
void digestCommand(client *c);
|
||||
|
||||
#if defined(__GNUC__)
|
||||
void *calloc(size_t count, size_t size) __attribute__ ((deprecated));
|
||||
|
|
|
|||
154
src/t_string.c
154
src/t_string.c
|
|
@ -8,6 +8,7 @@
|
|||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include "xxhash.h"
|
||||
#include <math.h> /* isnan(), isinf() */
|
||||
|
||||
/* Forward declarations */
|
||||
|
|
@ -35,11 +36,15 @@ static int checkStringLength(client *c, long long size, long long append) {
|
|||
* options and variants. This function is called in order to implement the
|
||||
* following commands: SET, SETEX, PSETEX, SETNX, GETSET.
|
||||
*
|
||||
* 'flags' changes the behavior of the command (NX, XX or GET, see below).
|
||||
* 'flags' changes the behavior of the command (NX, XX, GET, IFEQ, IFNE, IFDEQ
|
||||
* or IFDNE - see below).
|
||||
*
|
||||
* 'expire' represents an expire to set in form of a Redis object as passed
|
||||
* by the user. It is interpreted according to the specified 'unit'.
|
||||
*
|
||||
* 'match_value' is a value to check against if any of IFEQ/IFNE/IFDEQ/IFDNE is
|
||||
* present.
|
||||
*
|
||||
* 'ok_reply' and 'abort_reply' is what the function will reply to the client
|
||||
* if the operation is performed, or when it is not because of NX or
|
||||
* XX flags.
|
||||
|
|
@ -57,6 +62,10 @@ static int checkStringLength(client *c, long long size, long long append) {
|
|||
#define OBJ_EXAT (1<<6) /* Set if timestamp in second is given */
|
||||
#define OBJ_PXAT (1<<7) /* Set if timestamp in ms is given */
|
||||
#define OBJ_PERSIST (1<<8) /* Set if we need to remove the ttl */
|
||||
#define OBJ_SET_IFEQ (1<<9) /* Set if value equals match value */
|
||||
#define OBJ_SET_IFNE (1<<10) /* Set if value does not equal match value */
|
||||
#define OBJ_SET_IFDEQ (1<<11) /* Set if current digest equals match digest */
|
||||
#define OBJ_SET_IFDNE (1<<12) /* Set if current digest does not equal match digest */
|
||||
|
||||
/* Forward declaration */
|
||||
static int getExpireMillisecondsOrReply(client *c, robj *expire, int flags, int unit, long long *milliseconds);
|
||||
|
|
@ -71,7 +80,7 @@ static int getExpireMillisecondsOrReply(client *c, robj *expire, int flags, int
|
|||
* so that both the database and the caller maintain valid references.
|
||||
*/
|
||||
void setGenericCommand(client *c, int flags, robj *key, robj **valref, robj *expire,
|
||||
int unit, robj *ok_reply, robj *abort_reply)
|
||||
int unit, robj *match_value, robj *ok_reply, robj *abort_reply)
|
||||
{
|
||||
long long milliseconds = 0; /* initialized to avoid any harmless warning */
|
||||
int found = 0;
|
||||
|
|
@ -89,7 +98,7 @@ void setGenericCommand(client *c, int flags, robj *key, robj **valref, robj *exp
|
|||
found = (lookupKeyWriteWithLink(c->db,key,&link) != NULL);
|
||||
|
||||
if ((flags & OBJ_SET_NX && found) ||
|
||||
(flags & OBJ_SET_XX && !found))
|
||||
(flags & (OBJ_SET_XX | OBJ_SET_IFEQ | OBJ_SET_IFDEQ) && !found))
|
||||
{
|
||||
if (!(flags & OBJ_SET_GET)) {
|
||||
addReply(c, abort_reply ? abort_reply : shared.null[c->resp]);
|
||||
|
|
@ -97,6 +106,41 @@ void setGenericCommand(client *c, int flags, robj *key, robj **valref, robj *exp
|
|||
return;
|
||||
}
|
||||
|
||||
/* Handle conditional set operations - only set if key is found and condition
|
||||
* is met - otherwise return nil. */
|
||||
if (found && (flags & (OBJ_SET_IFEQ | OBJ_SET_IFNE | OBJ_SET_IFDEQ | OBJ_SET_IFDNE))) {
|
||||
kvobj *current = lookupKeyRead(c->db, key);
|
||||
if (checkType(c, current, OBJ_STRING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (flags & OBJ_SET_IFEQ || flags & OBJ_SET_IFNE) {
|
||||
robj *current_decoded = getDecodedObject(current);
|
||||
int condition = (flags & OBJ_SET_IFEQ) ?
|
||||
sdscmp(current_decoded->ptr, match_value->ptr) == 0 :
|
||||
sdscmp(current_decoded->ptr, match_value->ptr) != 0;
|
||||
decrRefCount(current_decoded);
|
||||
if (!condition) {
|
||||
if (!(flags & OBJ_SET_GET)) {
|
||||
addReply(c, abort_reply ? abort_reply : shared.null[c->resp]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else if (flags & OBJ_SET_IFDEQ || flags & OBJ_SET_IFDNE) {
|
||||
sds current_digest = stringDigest(current);
|
||||
int condition = flags & OBJ_SET_IFDEQ ?
|
||||
sdscmp(current_digest, match_value->ptr) == 0 :
|
||||
sdscmp(current_digest, match_value->ptr) != 0;
|
||||
sdsfree(current_digest);
|
||||
if (!condition) {
|
||||
if (!(flags & OBJ_SET_GET)) {
|
||||
addReply(c, abort_reply ? abort_reply : shared.null[c->resp]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* When expire is not NULL, we avoid deleting the TTL so it can be updated later instead of being deleted and then created again. */
|
||||
setkey_flags |= ((flags & OBJ_KEEPTTL) || expire) ? SETKEY_KEEPTTL : 0;
|
||||
setkey_flags |= found ? SETKEY_ALREADY_EXIST : SETKEY_DOESNT_EXIST;
|
||||
|
|
@ -194,7 +238,7 @@ static int getExpireMillisecondsOrReply(client *c, robj *expire, int flags, int
|
|||
* string arguments used in SET and GET command.
|
||||
*
|
||||
* Get specific commands - PERSIST/DEL
|
||||
* Set specific commands - XX/NX/GET
|
||||
* Set specific commands - XX/NX/GET/IFEQ/IFNE/IFDEQ/IFDNE
|
||||
* Common commands - EX/EXAT/PX/PXAT/KEEPTTL
|
||||
*
|
||||
* Function takes pointers to client, flags, unit, pointer to pointer of expire obj if needed
|
||||
|
|
@ -204,22 +248,36 @@ static int getExpireMillisecondsOrReply(client *c, robj *expire, int flags, int
|
|||
*
|
||||
* Input flags are updated upon parsing the arguments. Unit and expire are updated if there are any
|
||||
* EX/EXAT/PX/PXAT arguments. Unit is updated to millisecond if PX/PXAT is set.
|
||||
* match_value is updated if any of IFEQ/IFNE/IFDEQ/IFDNE is set.
|
||||
*/
|
||||
int parseExtendedStringArgumentsOrReply(client *c, int *flags, int *unit, robj **expire, int command_type) {
|
||||
int parseExtendedStringArgumentsOrReply(client *c, int *flags, int *unit, robj **expire, robj **match_value, int command_type) {
|
||||
|
||||
int j = command_type == COMMAND_GET ? 2 : 3;
|
||||
|
||||
/* We can have either none or exactly one of these conditionals as they are
|
||||
* mutually exclusive. We'll make sure to check if none of the other flags
|
||||
* are already set if we are going to set one of them. This is done via the
|
||||
* check:
|
||||
*
|
||||
* if (opt == OBJ_SET_XXX && !(*flags & (cond_mut_excl & ~OBJ_SET_XXX)))
|
||||
*
|
||||
* A bit ugly - but concise.
|
||||
*/
|
||||
int cond_mut_excl = OBJ_SET_NX | OBJ_SET_XX | OBJ_SET_IFEQ | OBJ_SET_IFNE |
|
||||
OBJ_SET_IFDEQ | OBJ_SET_IFDNE;
|
||||
|
||||
for (; j < c->argc; j++) {
|
||||
char *opt = c->argv[j]->ptr;
|
||||
robj *next = (j == c->argc-1) ? NULL : c->argv[j+1];
|
||||
|
||||
if ((opt[0] == 'n' || opt[0] == 'N') &&
|
||||
(opt[1] == 'x' || opt[1] == 'X') && opt[2] == '\0' &&
|
||||
!(*flags & OBJ_SET_XX) && (command_type == COMMAND_SET))
|
||||
!(*flags & (cond_mut_excl & ~OBJ_SET_NX)) && (command_type == COMMAND_SET))
|
||||
{
|
||||
*flags |= OBJ_SET_NX;
|
||||
} else if ((opt[0] == 'x' || opt[0] == 'X') &&
|
||||
(opt[1] == 'x' || opt[1] == 'X') && opt[2] == '\0' &&
|
||||
!(*flags & OBJ_SET_NX) && (command_type == COMMAND_SET))
|
||||
!(*flags & (cond_mut_excl & ~OBJ_SET_XX)) && (command_type == COMMAND_SET))
|
||||
{
|
||||
*flags |= OBJ_SET_XX;
|
||||
} else if ((opt[0] == 'g' || opt[0] == 'G') &&
|
||||
|
|
@ -281,6 +339,34 @@ int parseExtendedStringArgumentsOrReply(client *c, int *flags, int *unit, robj *
|
|||
*unit = UNIT_MILLISECONDS;
|
||||
*expire = next;
|
||||
j++;
|
||||
} else if (!strcasecmp(opt, "ifeq") && next &&
|
||||
!(*flags & (cond_mut_excl & ~OBJ_SET_IFEQ)) &&
|
||||
(command_type == COMMAND_SET))
|
||||
{
|
||||
*flags |= OBJ_SET_IFEQ;
|
||||
*match_value = next;
|
||||
j++;
|
||||
} else if (!strcasecmp(opt, "ifne") && next &&
|
||||
!(*flags & (cond_mut_excl & ~OBJ_SET_IFNE)) &&
|
||||
(command_type == COMMAND_SET))
|
||||
{
|
||||
*flags |= OBJ_SET_IFNE;
|
||||
*match_value = next;
|
||||
j++;
|
||||
} else if (!strcasecmp(opt, "ifdeq") && next &&
|
||||
!(*flags & (cond_mut_excl & ~OBJ_SET_IFDEQ)) &&
|
||||
(command_type == COMMAND_SET))
|
||||
{
|
||||
*flags |= OBJ_SET_IFDEQ;
|
||||
*match_value = next;
|
||||
j++;
|
||||
} else if (!strcasecmp(opt, "ifdne") && next &&
|
||||
!(*flags & (cond_mut_excl & ~OBJ_SET_IFDNE)) &&
|
||||
(command_type == COMMAND_SET))
|
||||
{
|
||||
*flags |= OBJ_SET_IFDNE;
|
||||
*match_value = next;
|
||||
j++;
|
||||
} else {
|
||||
addReplyErrorObject(c,shared.syntaxerr);
|
||||
return C_ERR;
|
||||
|
|
@ -290,33 +376,36 @@ int parseExtendedStringArgumentsOrReply(client *c, int *flags, int *unit, robj *
|
|||
}
|
||||
|
||||
/* SET key value [NX] [XX] [KEEPTTL] [GET] [EX <seconds>] [PX <milliseconds>]
|
||||
* [EXAT <seconds-timestamp>][PXAT <milliseconds-timestamp>] */
|
||||
* [EXAT <seconds-timestamp>][PXAT <milliseconds-timestamp>]
|
||||
* [IFEQ <match-value>|IFNE <match-value>|IFDEQ <match-digest>|
|
||||
* IFDNE <match-digest>]*/
|
||||
void setCommand(client *c) {
|
||||
robj *expire = NULL;
|
||||
robj *match_value = NULL;
|
||||
int unit = UNIT_SECONDS;
|
||||
int flags = OBJ_NO_FLAGS;
|
||||
|
||||
if (parseExtendedStringArgumentsOrReply(c,&flags,&unit,&expire,COMMAND_SET) != C_OK) {
|
||||
if (parseExtendedStringArgumentsOrReply(c,&flags,&unit,&expire,&match_value,COMMAND_SET) != C_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
c->argv[2] = tryObjectEncoding(c->argv[2]);
|
||||
setGenericCommand(c,flags,c->argv[1],&(c->argv[2]),expire,unit,NULL,NULL);
|
||||
setGenericCommand(c,flags,c->argv[1],&(c->argv[2]),expire,unit,match_value,NULL,NULL);
|
||||
}
|
||||
|
||||
void setnxCommand(client *c) {
|
||||
c->argv[2] = tryObjectEncoding(c->argv[2]);
|
||||
setGenericCommand(c, OBJ_SET_NX, c->argv[1], &(c->argv[2]), NULL, 0, shared.cone, shared.czero);
|
||||
setGenericCommand(c, OBJ_SET_NX, c->argv[1], &(c->argv[2]), NULL, 0, NULL, shared.cone, shared.czero);
|
||||
}
|
||||
|
||||
void setexCommand(client *c) {
|
||||
c->argv[3] = tryObjectEncoding(c->argv[3]);
|
||||
setGenericCommand(c, OBJ_EX, c->argv[1], &(c->argv[3]), c->argv[2], UNIT_SECONDS, NULL, NULL);
|
||||
setGenericCommand(c, OBJ_EX, c->argv[1], &(c->argv[3]), c->argv[2], UNIT_SECONDS, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
void psetexCommand(client *c) {
|
||||
c->argv[3] = tryObjectEncoding(c->argv[3]);
|
||||
setGenericCommand(c, OBJ_PX, c->argv[1], &(c->argv[3]), c->argv[2], UNIT_MILLISECONDS, NULL, NULL);
|
||||
setGenericCommand(c, OBJ_PX, c->argv[1], &(c->argv[3]), c->argv[2], UNIT_MILLISECONDS, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
int getGenericCommand(client *c) {
|
||||
|
|
@ -362,7 +451,7 @@ void getexCommand(client *c) {
|
|||
int unit = UNIT_SECONDS;
|
||||
int flags = OBJ_NO_FLAGS;
|
||||
|
||||
if (parseExtendedStringArgumentsOrReply(c,&flags,&unit,&expire,COMMAND_GET) != C_OK) {
|
||||
if (parseExtendedStringArgumentsOrReply(c,&flags,&unit,&expire,NULL,COMMAND_GET) != C_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -962,3 +1051,40 @@ cleanup:
|
|||
return;
|
||||
}
|
||||
|
||||
/* Return the xxh3 hash of a string object as a hex string stored in an sds.
|
||||
* The user is responsible for freeing the sds. */
|
||||
sds stringDigest(robj *o) {
|
||||
serverAssert(o && o->type == OBJ_STRING);
|
||||
|
||||
XXH64_hash_t hash = 0;
|
||||
if (sdsEncodedObject(o)) {
|
||||
hash = XXH3_64bits(o->ptr, sdslen(o->ptr));
|
||||
} else if (o->encoding == OBJ_ENCODING_INT) {
|
||||
char buf[LONG_STR_SIZE];
|
||||
size_t len = ll2string(buf,sizeof(buf),(long)o->ptr);
|
||||
hash = XXH3_64bits(buf, len);
|
||||
} else {
|
||||
serverPanic("Wrong obj->encoding stringDigest()");
|
||||
}
|
||||
|
||||
sds hexhash = sdsempty();
|
||||
hexhash = sdscatprintf(hexhash, "%" PRIx64, hash);
|
||||
return hexhash;
|
||||
}
|
||||
|
||||
/* DIGEST key
|
||||
*
|
||||
* Return digest of the key's value computed via XXH3 hash. The key must be a
|
||||
* STRING object. */
|
||||
void digestCommand(client *c) {
|
||||
kvobj *o;
|
||||
|
||||
if ((o = lookupKeyReadOrReply(c, c->argv[1], shared.null[c->resp])) == NULL)
|
||||
return;
|
||||
|
||||
if (checkType(c,o,OBJ_STRING))
|
||||
return;
|
||||
|
||||
addReplyBulkSds(c, stringDigest(o));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -67,18 +67,18 @@
|
|||
"ZRANGE k 1 2 rev " "[BYSCORE|BYLEX] [LIMIT offset count] [WITHSCORES]"
|
||||
"ZRANGE k 1 2 WITHSCORES " "[BYSCORE|BYLEX] [REV] [LIMIT offset count]"
|
||||
|
||||
# Optional one-of args with parameters: SET key value [NX|XX] [GET] [EX seconds|PX milliseconds|EXAT unix-time-seconds|PXAT unix-time-milliseconds|KEEPTTL]
|
||||
"SET key value " "[NX|XX] [GET] [EX seconds|PX milliseconds|EXAT unix-time-seconds|PXAT unix-time-milliseconds|KEEPTTL]"
|
||||
"SET key value EX" "[NX|XX] [GET] [EX seconds|PX milliseconds|EXAT unix-time-seconds|PXAT unix-time-milliseconds|KEEPTTL]"
|
||||
"SET key value EX " "seconds [NX|XX] [GET]"
|
||||
"SET key value EX 23 " "[NX|XX] [GET]"
|
||||
"SET key value EXAT" "[NX|XX] [GET] [EX seconds|PX milliseconds|EXAT unix-time-seconds|PXAT unix-time-milliseconds|KEEPTTL]"
|
||||
"SET key value EXAT " "unix-time-seconds [NX|XX] [GET]"
|
||||
"SET key value PX" "[NX|XX] [GET] [EX seconds|PX milliseconds|EXAT unix-time-seconds|PXAT unix-time-milliseconds|KEEPTTL]"
|
||||
"SET key value PX " "milliseconds [NX|XX] [GET]"
|
||||
"SET key value PXAT" "[NX|XX] [GET] [EX seconds|PX milliseconds|EXAT unix-time-seconds|PXAT unix-time-milliseconds|KEEPTTL]"
|
||||
"SET key value PXAT " "unix-time-milliseconds [NX|XX] [GET]"
|
||||
"SET key value KEEPTTL " "[NX|XX] [GET]"
|
||||
# Optional one-of args with parameters: SET key value [NX|XX|IFEQ ifeq-value|IFNE ifne-value|IFDEQ ifdeq-digest|IFDNE ifdne-digest] [GET] [EX seconds|PX milliseconds|EXAT unix-time-seconds|PXAT unix-time-milliseconds|KEEPTTL]
|
||||
"SET key value " "[NX|XX|IFEQ ifeq-value|IFNE ifne-value|IFDEQ ifdeq-digest|IFDNE ifdne-digest] [GET] [EX seconds|PX milliseconds|EXAT unix-time-seconds|PXAT unix-time-milliseconds|KEEPTTL]"
|
||||
"SET key value EX" "[NX|XX|IFEQ ifeq-value|IFNE ifne-value|IFDEQ ifdeq-digest|IFDNE ifdne-digest] [GET] [EX seconds|PX milliseconds|EXAT unix-time-seconds|PXAT unix-time-milliseconds|KEEPTTL]"
|
||||
"SET key value EX " "seconds [NX|XX|IFEQ ifeq-value|IFNE ifne-value|IFDEQ ifdeq-digest|IFDNE ifdne-digest] [GET]"
|
||||
"SET key value EX 23 " "[NX|XX|IFEQ ifeq-value|IFNE ifne-value|IFDEQ ifdeq-digest|IFDNE ifdne-digest] [GET]"
|
||||
"SET key value EXAT" "[NX|XX|IFEQ ifeq-value|IFNE ifne-value|IFDEQ ifdeq-digest|IFDNE ifdne-digest] [GET] [EX seconds|PX milliseconds|EXAT unix-time-seconds|PXAT unix-time-milliseconds|KEEPTTL]"
|
||||
"SET key value EXAT " "unix-time-seconds [NX|XX|IFEQ ifeq-value|IFNE ifne-value|IFDEQ ifdeq-digest|IFDNE ifdne-digest] [GET]"
|
||||
"SET key value PX" "[NX|XX|IFEQ ifeq-value|IFNE ifne-value|IFDEQ ifdeq-digest|IFDNE ifdne-digest] [GET] [EX seconds|PX milliseconds|EXAT unix-time-seconds|PXAT unix-time-milliseconds|KEEPTTL]"
|
||||
"SET key value PX " "milliseconds [NX|XX|IFEQ ifeq-value|IFNE ifne-value|IFDEQ ifdeq-digest|IFDNE ifdne-digest] [GET]"
|
||||
"SET key value PXAT" "[NX|XX|IFEQ ifeq-value|IFNE ifne-value|IFDEQ ifdeq-digest|IFDNE ifdne-digest] [GET] [EX seconds|PX milliseconds|EXAT unix-time-seconds|PXAT unix-time-milliseconds|KEEPTTL]"
|
||||
"SET key value PXAT " "unix-time-milliseconds [NX|XX|IFEQ ifeq-value|IFNE ifne-value|IFDEQ ifdeq-digest|IFDNE ifdne-digest] [GET]"
|
||||
"SET key value KEEPTTL " "[NX|XX|IFEQ ifeq-value|IFNE ifne-value|IFDEQ ifdeq-digest|IFDNE ifdne-digest] [GET]"
|
||||
"SET key value XX " "[GET] [EX seconds|PX milliseconds|EXAT unix-time-seconds|PXAT unix-time-milliseconds|KEEPTTL]"
|
||||
|
||||
# If an input word can't be matched, stop hinting.
|
||||
|
|
|
|||
|
|
@ -743,4 +743,673 @@ if {[string match {*jemalloc*} [s mem_allocator]]} {
|
|||
}
|
||||
} {} {needs:debug}
|
||||
}
|
||||
|
||||
test {DIGEST basic usage with plain string} {
|
||||
r set mykey "hello world"
|
||||
set digest [r digest mykey]
|
||||
# Ensure reply is hex string
|
||||
assert {[string is wideinteger -strict "0x$digest"]}
|
||||
}
|
||||
|
||||
test {DIGEST with empty string} {
|
||||
r set mykey ""
|
||||
set digest [r digest mykey]
|
||||
assert {[string is wideinteger -strict "0x$digest"]}
|
||||
}
|
||||
|
||||
test {DIGEST with integer-encoded value} {
|
||||
r set mykey 12345
|
||||
assert_encoding int mykey
|
||||
set digest [r digest mykey]
|
||||
assert {[string is wideinteger -strict "0x$digest"]}
|
||||
}
|
||||
|
||||
test {DIGEST with negative integer} {
|
||||
r set mykey -999
|
||||
assert_encoding int mykey
|
||||
set digest [r digest mykey]
|
||||
assert {[string is wideinteger -strict "0x$digest"]}
|
||||
}
|
||||
|
||||
test {DIGEST returns consistent hash for same value} {
|
||||
r set mykey "test string"
|
||||
set digest1 [r digest mykey]
|
||||
set digest2 [r digest mykey]
|
||||
assert_equal $digest1 $digest2
|
||||
}
|
||||
|
||||
test {DIGEST returns same hash for same content in different keys} {
|
||||
r set key1 "identical"
|
||||
r set key2 "identical"
|
||||
set digest1 [r digest key1]
|
||||
set digest2 [r digest key2]
|
||||
assert_equal $digest1 $digest2
|
||||
}
|
||||
|
||||
test {DIGEST returns different hash for different values} {
|
||||
r set key1 "value1"
|
||||
r set key2 "value2"
|
||||
set digest1 [r digest key1]
|
||||
set digest2 [r digest key2]
|
||||
assert {$digest1 != $digest2}
|
||||
}
|
||||
|
||||
test {DIGEST with binary data} {
|
||||
r set mykey "\x00\x01\x02\x03\xff\xfe"
|
||||
set digest [r digest mykey]
|
||||
assert {[string is wideinteger -strict "0x$digest"]}
|
||||
}
|
||||
|
||||
test {DIGEST with unicode characters} {
|
||||
r set mykey "Hello 世界"
|
||||
set digest [r digest mykey]
|
||||
assert {[string is wideinteger -strict "0x$digest"]}
|
||||
}
|
||||
|
||||
test {DIGEST with very long string} {
|
||||
set longstring [string repeat "Lorem ipsum dolor sit amet. " 1000]
|
||||
r set mykey $longstring
|
||||
set digest [r digest mykey]
|
||||
assert {[string is wideinteger -strict "0x$digest"]}
|
||||
}
|
||||
|
||||
test {DIGEST against non-existing key} {
|
||||
r del nonexistent
|
||||
assert_equal {} [r digest nonexistent]
|
||||
}
|
||||
|
||||
test {DIGEST against wrong type (list)} {
|
||||
r del mylist
|
||||
r lpush mylist "element"
|
||||
assert_error "*WRONGTYPE*" {r digest mylist}
|
||||
}
|
||||
|
||||
test {DIGEST against wrong type (hash)} {
|
||||
r del myhash
|
||||
r hset myhash field value
|
||||
assert_error "*WRONGTYPE*" {r digest myhash}
|
||||
}
|
||||
|
||||
test {DIGEST against wrong type (set)} {
|
||||
r del myset
|
||||
r sadd myset member
|
||||
assert_error "*WRONGTYPE*" {r digest myset}
|
||||
}
|
||||
|
||||
test {DIGEST against wrong type (zset)} {
|
||||
r del myzset
|
||||
r zadd myzset 1 member
|
||||
assert_error "*WRONGTYPE*" {r digest myzset}
|
||||
}
|
||||
|
||||
test {DIGEST wrong number of arguments} {
|
||||
assert_error "*wrong number of arguments*" {r digest}
|
||||
assert_error "*wrong number of arguments*" {r digest key1 key2}
|
||||
}
|
||||
|
||||
test {DIGEST with special characters and whitespace} {
|
||||
r set mykey " spaces \t\n\r"
|
||||
set digest [r digest mykey]
|
||||
assert {[string is wideinteger -strict "0x$digest"]}
|
||||
}
|
||||
|
||||
test {DIGEST consistency across SET operations} {
|
||||
r set mykey "original"
|
||||
set digest1 [r digest mykey]
|
||||
|
||||
r set mykey "changed"
|
||||
set digest2 [r digest mykey]
|
||||
assert {$digest1 != $digest2}
|
||||
|
||||
r set mykey "original"
|
||||
set digest3 [r digest mykey]
|
||||
assert_equal $digest1 $digest3
|
||||
}
|
||||
|
||||
test {DELEX basic usage without conditions} {
|
||||
r set mykey "hello"
|
||||
assert_equal 1 [r delex mykey]
|
||||
|
||||
r hset myhash f v
|
||||
assert_equal 1 [r delex myhash]
|
||||
|
||||
r zadd mystr 1 m
|
||||
assert_equal 1 [r delex mystr]
|
||||
}
|
||||
|
||||
test {DELEX basic usage with IFEQ} {
|
||||
r set mykey "hello"
|
||||
assert_equal 1 [r delex mykey IFEQ "hello"]
|
||||
assert_equal 0 [r exists mykey]
|
||||
|
||||
r set mykey "hello"
|
||||
assert_equal 0 [r delex mykey IFEQ "world"]
|
||||
assert_equal 1 [r exists mykey]
|
||||
assert_equal "hello" [r get mykey]
|
||||
}
|
||||
|
||||
test {DELEX basic usage with IFNE} {
|
||||
r set mykey "hello"
|
||||
assert_equal 1 [r delex mykey IFNE "world"]
|
||||
assert_equal 0 [r exists mykey]
|
||||
|
||||
r set mykey "hello"
|
||||
assert_equal 0 [r delex mykey IFNE "hello"]
|
||||
assert_equal 1 [r exists mykey]
|
||||
assert_equal "hello" [r get mykey]
|
||||
}
|
||||
|
||||
test {DELEX basic usage with IFDEQ} {
|
||||
r set mykey "hello"
|
||||
set digest [r digest mykey]
|
||||
assert_equal 1 [r delex mykey IFDEQ $digest]
|
||||
assert_equal 0 [r exists mykey]
|
||||
|
||||
r set mykey "hello"
|
||||
set wrong_digest [format %x [expr [scan [r digest mykey] %x] + 1]]
|
||||
assert_equal 0 [r delex mykey IFDEQ $wrong_digest]
|
||||
assert_equal 1 [r exists mykey]
|
||||
assert_equal "hello" [r get mykey]
|
||||
}
|
||||
|
||||
test {DELEX basic usage with IFDNE} {
|
||||
r set mykey "hello"
|
||||
set wrong_digest [format %x [expr [scan [r digest mykey] %x] + 1]]
|
||||
assert_equal 1 [r delex mykey IFDNE $wrong_digest]
|
||||
assert_equal 0 [r exists mykey]
|
||||
|
||||
r set mykey "hello"
|
||||
set digest [r digest mykey]
|
||||
assert_equal 0 [r delex mykey IFDNE $digest]
|
||||
assert_equal 1 [r exists mykey]
|
||||
assert_equal "hello" [r get mykey]
|
||||
}
|
||||
|
||||
test {DELEX with non-existing key} {
|
||||
r del nonexistent
|
||||
assert_equal 0 [r delex nonexistent IFEQ "hello"]
|
||||
assert_equal 0 [r delex nonexistent IFNE "hello"]
|
||||
assert_equal 0 [r delex nonexistent IFDEQ 1234567890]
|
||||
assert_equal 0 [r delex nonexistent IFDNE 1234567890]
|
||||
}
|
||||
|
||||
test {DELEX with empty string value} {
|
||||
r set mykey ""
|
||||
assert_equal 1 [r delex mykey IFEQ ""]
|
||||
assert_equal 0 [r exists mykey]
|
||||
|
||||
r set mykey ""
|
||||
assert_equal 0 [r delex mykey IFEQ "notempty"]
|
||||
assert_equal 1 [r exists mykey]
|
||||
}
|
||||
|
||||
test {DELEX with integer-encoded value} {
|
||||
r set mykey 12345
|
||||
assert_encoding int mykey
|
||||
assert_equal 1 [r delex mykey IFEQ "12345"]
|
||||
assert_equal 0 [r exists mykey]
|
||||
|
||||
r set mykey 12345
|
||||
assert_encoding int mykey
|
||||
assert_equal 0 [r delex mykey IFEQ "54321"]
|
||||
assert_equal 1 [r exists mykey]
|
||||
}
|
||||
|
||||
test {DELEX with negative integer} {
|
||||
r set mykey -999
|
||||
assert_encoding int mykey
|
||||
assert_equal 1 [r delex mykey IFEQ "-999"]
|
||||
assert_equal 0 [r exists mykey]
|
||||
}
|
||||
|
||||
test {DELEX with binary data} {
|
||||
r set mykey "\x00\x01\x02\x03\xff\xfe"
|
||||
assert_equal 1 [r delex mykey IFEQ "\x00\x01\x02\x03\xff\xfe"]
|
||||
assert_equal 0 [r exists mykey]
|
||||
|
||||
r set mykey "\x00\x01\x02\x03\xff\xfe"
|
||||
assert_equal 0 [r delex mykey IFEQ "\x00\x01\x02\x03\xff\xff"]
|
||||
assert_equal 1 [r exists mykey]
|
||||
}
|
||||
|
||||
test {DELEX with unicode characters} {
|
||||
r set mykey "Hello 世界"
|
||||
assert_equal 1 [r delex mykey IFEQ "Hello 世界"]
|
||||
assert_equal 0 [r exists mykey]
|
||||
|
||||
r set mykey "Hello 世界"
|
||||
assert_equal 0 [r delex mykey IFEQ "Hello World"]
|
||||
assert_equal 1 [r exists mykey]
|
||||
}
|
||||
|
||||
test {DELEX with very long string} {
|
||||
set longstring [string repeat "Lorem ipsum dolor sit amet. " 1000]
|
||||
r set mykey $longstring
|
||||
assert_equal 1 [r delex mykey IFEQ $longstring]
|
||||
assert_equal 0 [r exists mykey]
|
||||
}
|
||||
|
||||
test {DELEX against wrong type} {
|
||||
r del mylist
|
||||
r lpush mylist "element"
|
||||
assert_error "*ERR*" {r delex mylist IFEQ "element"}
|
||||
|
||||
r del myhash
|
||||
r hset myhash field value
|
||||
assert_error "*ERR*" {r delex myhash IFEQ "value"}
|
||||
|
||||
r del myset
|
||||
r sadd myset member
|
||||
assert_error "*ERR*" {r delex myset IFEQ "member"}
|
||||
|
||||
r del myzset
|
||||
r zadd myzset 1 member
|
||||
assert_error "*ERR*" {r delex myzset IFEQ "member"}
|
||||
}
|
||||
|
||||
test {DELEX wrong number of arguments} {
|
||||
r del key1
|
||||
assert_equal 0 [r delex key1 IFEQ]
|
||||
|
||||
r set key1 x
|
||||
assert_error "*wrong number of arguments*" {r delex key1 IFEQ}
|
||||
assert_error "*wrong number of arguments*" {r delex key1 IFEQ value1 extra}
|
||||
}
|
||||
|
||||
test {DELEX invalid condition} {
|
||||
r set mykey "hello"
|
||||
assert_error "*Invalid condition*" {r delex mykey INVALID "hello"}
|
||||
assert_error "*Invalid condition*" {r delex mykey IF "hello"}
|
||||
assert_error "*Invalid condition*" {r delex mykey EQ "hello"}
|
||||
}
|
||||
|
||||
test {DELEX with special characters and whitespace} {
|
||||
r set mykey " spaces \t\n\r"
|
||||
assert_equal 1 [r delex mykey IFEQ " spaces \t\n\r"]
|
||||
assert_equal 0 [r exists mykey]
|
||||
}
|
||||
|
||||
test {DELEX digest consistency with same content} {
|
||||
r set key1 "identical"
|
||||
r set key2 "identical"
|
||||
set digest1 [r digest key1]
|
||||
set digest2 [r digest key2]
|
||||
assert_equal $digest1 $digest2
|
||||
|
||||
# Both should be deletable with the same digest
|
||||
assert_equal 1 [r delex key1 IFDEQ $digest2]
|
||||
assert_equal 1 [r delex key2 IFDEQ $digest1]
|
||||
}
|
||||
|
||||
test {DELEX digest with different content} {
|
||||
r set key1 "value1"
|
||||
r set key2 "value2"
|
||||
set digest1 [r digest key1]
|
||||
set digest2 [r digest key2]
|
||||
assert {$digest1 != $digest2}
|
||||
|
||||
# Should not be able to delete with wrong digest
|
||||
assert_equal 0 [r delex key1 IFDEQ $digest2]
|
||||
assert_equal 0 [r delex key2 IFDEQ $digest1]
|
||||
|
||||
# Should be able to delete with correct digest
|
||||
assert_equal 1 [r delex key1 IFDEQ $digest1]
|
||||
assert_equal 1 [r delex key2 IFDEQ $digest2]
|
||||
}
|
||||
|
||||
test {DELEX propagate as DEL command to replica} {
|
||||
set repl [attach_to_replication_stream]
|
||||
r set foo bar
|
||||
r delex foo IFEQ bar
|
||||
assert_replication_stream $repl {
|
||||
{select *}
|
||||
{set foo bar}
|
||||
{del foo}
|
||||
}
|
||||
close_replication_stream $repl
|
||||
} {} {needs:repl}
|
||||
|
||||
test {DELEX does not propagate when condition not met} {
|
||||
set repl [attach_to_replication_stream]
|
||||
r set foo bar
|
||||
r delex foo IFEQ baz
|
||||
r set foo bar2
|
||||
assert_replication_stream $repl {
|
||||
{select *}
|
||||
{set foo bar}
|
||||
{set foo bar2}
|
||||
}
|
||||
close_replication_stream $repl
|
||||
} {} {needs:repl}
|
||||
|
||||
test {DELEX with integer that looks like string} {
|
||||
# Set as integer
|
||||
r set key1 123
|
||||
assert_encoding int key1
|
||||
assert_equal 1 [r delex key1 IFEQ "123"]
|
||||
assert_equal 0 [r exists key1]
|
||||
|
||||
# Set as string
|
||||
r set key2 "123"
|
||||
assert_equal 1 [r delex key2 IFEQ "123"]
|
||||
assert_equal 0 [r exists key2]
|
||||
}
|
||||
|
||||
test {Extended SET with IFEQ - key exists and matches} {
|
||||
r set mykey "hello"
|
||||
assert_equal "OK" [r set mykey "world" IFEQ "hello"]
|
||||
assert_equal "world" [r get mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with IFEQ - key exists but doesn't match} {
|
||||
r set mykey "hello"
|
||||
assert_equal {} [r set mykey "world" IFEQ "different"]
|
||||
assert_equal "hello" [r get mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with IFEQ - key doesn't exist} {
|
||||
r del mykey
|
||||
assert_equal {} [r set mykey "world" IFEQ "hello"]
|
||||
assert_equal 0 [r exists mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with IFNE - key exists and doesn't match} {
|
||||
r set mykey "hello"
|
||||
assert_equal "OK" [r set mykey "world" IFNE "different"]
|
||||
assert_equal "world" [r get mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with IFNE - key exists and matches} {
|
||||
r set mykey "hello"
|
||||
assert_equal {} [r set mykey "world" IFNE "hello"]
|
||||
assert_equal "hello" [r get mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with IFNE - key doesn't exist} {
|
||||
r del mykey
|
||||
assert_equal "OK" [r set mykey "world" IFNE "hello"]
|
||||
assert_equal "world" [r get mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with IFDEQ - key exists and digest matches} {
|
||||
r set mykey "hello"
|
||||
set digest [r digest mykey]
|
||||
puts $digest
|
||||
assert_equal "OK" [r set mykey "world" IFDEQ $digest]
|
||||
assert_equal "world" [r get mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with IFDEQ - key exists but digest doesn't match} {
|
||||
r set mykey "hello"
|
||||
set wrong_digest [format %x [expr [scan [r digest mykey] %x] + 1]]
|
||||
assert_equal {} [r set mykey "world" IFDEQ $wrong_digest]
|
||||
assert_equal "hello" [r get mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with IFDEQ - key doesn't exist} {
|
||||
r del mykey
|
||||
set digest 1234567890
|
||||
assert_equal {} [r set mykey "world" IFDEQ $digest]
|
||||
assert_equal 0 [r exists mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with IFDNE - key exists and digest doesn't match} {
|
||||
r set mykey "hello"
|
||||
set wrong_digest [format %x [expr [scan [r digest mykey] %x] + 1]]
|
||||
assert_equal "OK" [r set mykey "world" IFDNE $wrong_digest]
|
||||
assert_equal "world" [r get mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with IFDNE - key exists and digest matches} {
|
||||
r set mykey "hello"
|
||||
set digest [r digest mykey]
|
||||
assert_equal {} [r set mykey "world" IFDNE $digest]
|
||||
assert_equal "hello" [r get mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with IFDNE - key doesn't exist} {
|
||||
r del mykey
|
||||
set digest 1234567890
|
||||
assert_equal "OK" [r set mykey "world" IFDNE $digest]
|
||||
assert_equal "world" [r get mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with IFEQ and GET - key exists and matches} {
|
||||
r set mykey "hello"
|
||||
assert_equal "hello" [r set mykey "world" IFEQ "hello" GET]
|
||||
assert_equal "world" [r get mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with IFEQ and GET - key exists but doesn't match} {
|
||||
r set mykey "hello"
|
||||
assert_equal "hello" [r set mykey "world" IFEQ "different" GET]
|
||||
assert_equal "hello" [r get mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with IFEQ and GET - key doesn't exist} {
|
||||
r del mykey
|
||||
assert_equal {} [r set mykey "world" IFEQ "hello" GET]
|
||||
assert_equal 0 [r exists mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with IFNE and GET - key exists and doesn't match} {
|
||||
r set mykey "hello"
|
||||
assert_equal "hello" [r set mykey "world" IFNE "different" GET]
|
||||
assert_equal "world" [r get mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with IFNE and GET - key exists and matches} {
|
||||
r set mykey "hello"
|
||||
assert_equal "hello" [r set mykey "world" IFNE "hello" GET]
|
||||
assert_equal "hello" [r get mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with IFNE and GET - key doesn't exist} {
|
||||
r del mykey
|
||||
assert_equal {} [r set mykey "world" IFNE "hello" GET]
|
||||
assert_equal "world" [r get mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with IFDEQ and GET - key exists and digest matches} {
|
||||
r set mykey "hello"
|
||||
set digest [r digest mykey]
|
||||
assert_equal "hello" [r set mykey "world" IFDEQ $digest GET]
|
||||
assert_equal "world" [r get mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with IFDEQ and GET - key exists but digest doesn't match} {
|
||||
r set mykey "hello"
|
||||
set wrong_digest [format %x [expr [scan [r digest mykey] %x] + 1]]
|
||||
assert_equal "hello" [r set mykey "world" IFDEQ $wrong_digest GET]
|
||||
assert_equal "hello" [r get mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with IFDEQ and GET - key doesn't exist} {
|
||||
r del mykey
|
||||
set digest 1234567890
|
||||
assert_equal {} [r set mykey "world" IFDEQ $digest GET]
|
||||
assert_equal 0 [r exists mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with IFDNE and GET - key exists and digest doesn't match} {
|
||||
r set mykey "hello"
|
||||
set wrong_digest [format %x [expr [scan [r digest mykey] %x] + 1]]
|
||||
assert_equal "hello" [r set mykey "world" IFDNE $wrong_digest GET]
|
||||
assert_equal "world" [r get mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with IFDNE and GET - key exists and digest matches} {
|
||||
r set mykey "hello"
|
||||
set digest [r digest mykey]
|
||||
assert_equal "hello" [r set mykey "world" IFDNE $digest GET]
|
||||
assert_equal "hello" [r get mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with IFDNE and GET - key doesn't exist} {
|
||||
r del mykey
|
||||
set digest 1234567890
|
||||
assert_equal {} [r set mykey "world" IFDNE $digest GET]
|
||||
assert_equal "world" [r get mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with IFEQ and expiration} {
|
||||
r set mykey "hello"
|
||||
assert_equal "OK" [r set mykey "world" IFEQ "hello" EX 10]
|
||||
assert_equal "world" [r get mykey]
|
||||
assert_range [r ttl mykey] 5 10
|
||||
}
|
||||
|
||||
test {Extended SET with IFNE and expiration} {
|
||||
r set mykey "hello"
|
||||
assert_equal "OK" [r set mykey "world" IFNE "different" EX 10]
|
||||
assert_equal "world" [r get mykey]
|
||||
assert_range [r ttl mykey] 5 10
|
||||
}
|
||||
|
||||
test {Extended SET with IFDEQ and expiration} {
|
||||
r set mykey "hello"
|
||||
set digest [r digest mykey]
|
||||
assert_equal "OK" [r set mykey "world" IFDEQ $digest EX 10]
|
||||
assert_equal "world" [r get mykey]
|
||||
assert_range [r ttl mykey] 5 10
|
||||
}
|
||||
|
||||
test {Extended SET with IFDNE and expiration} {
|
||||
r set mykey "hello"
|
||||
set wrong_digest [format %x [expr [scan [r digest mykey] %x] + 1]]
|
||||
assert_equal "OK" [r set mykey "world" IFDNE $wrong_digest EX 10]
|
||||
assert_equal "world" [r get mykey]
|
||||
assert_range [r ttl mykey] 5 10
|
||||
}
|
||||
|
||||
test {Extended SET with IFEQ against wrong type} {
|
||||
r del mylist
|
||||
r lpush mylist "element"
|
||||
assert_error "*WRONGTYPE*" {r set mylist "value" IFEQ "element"}
|
||||
}
|
||||
|
||||
test {Extended SET with IFNE against wrong type} {
|
||||
r del myhash
|
||||
r hset myhash field value
|
||||
assert_error "*WRONGTYPE*" {r set myhash "value" IFNE "value"}
|
||||
}
|
||||
|
||||
test {Extended SET with IFDEQ against wrong type} {
|
||||
r del myset
|
||||
r sadd myset member
|
||||
assert_error "*WRONGTYPE*" {r set myset "value" IFDEQ 1234567890}
|
||||
}
|
||||
|
||||
test {Extended SET with IFDNE against wrong type} {
|
||||
r del myzset
|
||||
r zadd myzset 1 member
|
||||
assert_error "*WRONGTYPE*" {r set myzset "value" IFDNE 1234567890}
|
||||
}
|
||||
|
||||
test {Extended SET with integer-encoded value and IFEQ} {
|
||||
r set mykey 12345
|
||||
assert_encoding int mykey
|
||||
assert_equal "OK" [r set mykey "world" IFEQ "12345"]
|
||||
assert_equal "world" [r get mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with integer-encoded value and IFNE} {
|
||||
r set mykey 12345
|
||||
assert_encoding int mykey
|
||||
assert_equal "OK" [r set mykey "world" IFNE "54321"]
|
||||
assert_equal "world" [r get mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with binary data and IFEQ} {
|
||||
r set mykey "\x00\x01\x02\x03\xff\xfe"
|
||||
assert_equal "OK" [r set mykey "world" IFEQ "\x00\x01\x02\x03\xff\xfe"]
|
||||
assert_equal "world" [r get mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with unicode characters and IFEQ} {
|
||||
r set mykey "Hello 世界"
|
||||
assert_equal "OK" [r set mykey "world" IFEQ "Hello 世界"]
|
||||
assert_equal "world" [r get mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with empty string and IFEQ} {
|
||||
r set mykey ""
|
||||
assert_equal "OK" [r set mykey "world" IFEQ ""]
|
||||
assert_equal "world" [r get mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with empty string and IFNE} {
|
||||
r set mykey ""
|
||||
assert_equal {} [r set mykey "world" IFNE ""]
|
||||
assert_equal "" [r get mykey]
|
||||
}
|
||||
|
||||
test {Extended SET case insensitive conditions} {
|
||||
r set mykey "hello"
|
||||
assert_equal "OK" [r set mykey "world" ifeq "hello"]
|
||||
assert_equal "world" [r get mykey]
|
||||
|
||||
r set mykey "hello"
|
||||
assert_equal "OK" [r set mykey "world" IfEq "hello"]
|
||||
assert_equal "world" [r get mykey]
|
||||
|
||||
r set mykey "hello"
|
||||
assert_equal "OK" [r set mykey "world" IFEQ "hello"]
|
||||
assert_equal "world" [r get mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with special characters and IFEQ} {
|
||||
r set mykey " spaces \t\n\r"
|
||||
assert_equal "OK" [r set mykey "world" IFEQ " spaces \t\n\r"]
|
||||
assert_equal "world" [r get mykey]
|
||||
}
|
||||
|
||||
test {Extended SET digest consistency with same content} {
|
||||
r set key1 "identical"
|
||||
r set key2 "identical"
|
||||
set digest1 [r digest key1]
|
||||
set digest2 [r digest key2]
|
||||
assert_equal $digest1 $digest2
|
||||
|
||||
# Both should be settable with the same digest
|
||||
assert_equal "OK" [r set key1 "new1" IFDEQ $digest1]
|
||||
assert_equal "OK" [r set key2 "new2" IFDEQ $digest2]
|
||||
assert_equal "new1" [r get key1]
|
||||
assert_equal "new2" [r get key2]
|
||||
}
|
||||
|
||||
test {Extended SET digest with different content} {
|
||||
r set key1 "value1"
|
||||
r set key2 "value2"
|
||||
set digest1 [r digest key1]
|
||||
set digest2 [r digest key2]
|
||||
assert {$digest1 != $digest2}
|
||||
|
||||
# Should not be able to set with wrong digest
|
||||
assert_equal {} [r set key1 "new1" IFDEQ $digest2]
|
||||
assert_equal {} [r set key2 "new2" IFDEQ $digest1]
|
||||
assert_equal "value1" [r get key1]
|
||||
assert_equal "value2" [r get key2]
|
||||
|
||||
# Should be able to set with correct digest
|
||||
assert_equal "OK" [r set key1 "new1" IFDEQ $digest1]
|
||||
assert_equal "OK" [r set key2 "new2" IFDEQ $digest2]
|
||||
assert_equal "new1" [r get key1]
|
||||
assert_equal "new2" [r get key2]
|
||||
}
|
||||
|
||||
test {Extended SET with very long string and IFEQ} {
|
||||
set longstring [string repeat "Lorem ipsum dolor sit amet. " 1000]
|
||||
r set mykey $longstring
|
||||
assert_equal "OK" [r set mykey "world" IFEQ $longstring]
|
||||
assert_equal "world" [r get mykey]
|
||||
}
|
||||
|
||||
test {Extended SET with negative digest} {
|
||||
r set mykey "test"
|
||||
set digest [r digest mykey]
|
||||
set wrong_digest [format %x [expr [scan [r digest mykey] %x] + 1]]
|
||||
assert_equal "OK" [r set mykey "world" IFDNE $wrong_digest]
|
||||
assert_equal "world" [r get mykey]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue