merge: upstream/unstable (preserve Hanzo brand, 117 commits)

This commit is contained in:
Hanzo AI
2026-06-02 10:00:55 -07:00
231 changed files with 33553 additions and 5897 deletions
+15 -12
View File
@@ -2,12 +2,16 @@ name: CI
on: [push, pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }}
cancel-in-progress: true
jobs:
test-ubuntu-latest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: make
# Fail build if there are warnings
# build with TLS just for compilation coverage
@@ -26,7 +30,7 @@ jobs:
test-sanitizer-address:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: make
# build with TLS module just for compilation coverage
run: make SANITIZER=address REDIS_CFLAGS='-Werror -DDEBUG_ASSERTIONS -DREDIS_TEST' BUILD_TLS=module
@@ -39,7 +43,7 @@ jobs:
runs-on: ubuntu-latest
container: debian:buster
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: make
run: |
sed -i 's|http://deb.debian.org/debian|http://archive.debian.org/debian|g' /etc/apt/sources.list
@@ -50,7 +54,7 @@ jobs:
build-macos-latest:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: make
# Fail build if there are warnings
# build with TLS just for compilation coverage
@@ -59,16 +63,16 @@ jobs:
build-32bit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: make
run: |
sudo apt-get update && sudo apt-get install libc6-dev-i386 gcc-multilib g++-multilib
sudo apt-get update && sudo apt-get install libc6-dev-i386 gcc-multilib
make REDIS_CFLAGS='-Werror' 32bit
build-libc-malloc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: make
run: make REDIS_CFLAGS='-Werror' MALLOC=libc
@@ -76,17 +80,17 @@ jobs:
runs-on: ubuntu-latest
container: quay.io/centos/centos:stream9
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: make
run: |
dnf -y install which gcc gcc-c++ make
dnf -y install which gcc make
make REDIS_CFLAGS='-Werror'
build-old-chain-jemalloc:
runs-on: ubuntu-latest
container: ubuntu:20.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: make
run: |
apt-get update
@@ -96,7 +100,6 @@ jobs:
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
apt-get update
apt-get install -y make gcc-4.8 g++-4.8
apt-get install -y make gcc-4.8
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 100
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 100
make CC=gcc REDIS_CFLAGS='-Werror'
+17 -5
View File
@@ -4,13 +4,21 @@ name: "Codecov"
# where each PR needs to be compared against the coverage of the head commit
on: [push, pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
code-coverage:
runs-on: ubuntu-22.04
if: ${{ github.repository == 'redis/redis' }}
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install lcov and run test
run: |
@@ -18,7 +26,11 @@ jobs:
make lcov
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./src/redis.info
files: ./src/redis.info
disable_search: true
fail_ci_if_error: true
+8 -4
View File
@@ -6,6 +6,10 @@ on:
# run weekly new vulnerability was added to the database
- cron: '0 0 * * 0'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }}
cancel-in-progress: true
jobs:
analyze:
name: Analyze
@@ -19,15 +23,15 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v3
uses: github/codeql-action/autobuild@v4
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v4
+14 -4
View File
@@ -6,17 +6,24 @@ on:
- cron: '0 0 * * *'
# Support manual execution
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }}
cancel-in-progress: true
jobs:
coverity:
if: github.repository == 'redis/redis'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- uses: actions/checkout@v6
- name: Download and extract the Coverity Build Tool
run: |
wget -q https://scan.coverity.com/download/cxx/linux64 --post-data "token=${{ secrets.COVERITY_SCAN_TOKEN }}&project=redis-unstable" -O cov-analysis-linux64.tar.gz
wget -q https://scan.coverity.com/download/cxx/linux64 --post-data "token=${COVERITY_SCAN_TOKEN}&project=redis-unstable" -O cov-analysis-linux64.tar.gz
mkdir cov-analysis-linux64
tar xzf cov-analysis-linux64.tar.gz --strip 1 -C cov-analysis-linux64
env:
COVERITY_SCAN_TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }}
- name: Install Redis dependencies
run: sudo apt install -y gcc tcl8.6 tclx procps libssl-dev
- name: Build with cov-build
@@ -26,7 +33,10 @@ jobs:
tar czvf cov-int.tgz cov-int
curl \
--form project=redis-unstable \
--form email=${{ secrets.COVERITY_SCAN_EMAIL }} \
--form token=${{ secrets.COVERITY_SCAN_TOKEN }} \
--form email="${COVERITY_SCAN_EMAIL}" \
--form token="${COVERITY_SCAN_TOKEN}" \
--form file=@cov-int.tgz \
https://scan.coverity.com/builds
env:
COVERITY_SCAN_EMAIL: ${{ secrets.COVERITY_SCAN_EMAIL }}
COVERITY_SCAN_TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }}
+59 -60
View File
@@ -47,12 +47,12 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
- name: make
run: make REDIS_CFLAGS='-Werror -DREDIS_TEST'
run: make REDIS_CFLAGS='-Werror -DREDIS_TEST -DDEBUG_ASSERTIONS'
- name: testprep
run: sudo apt-get install tcl8.6 tclx
- name: test
@@ -84,7 +84,7 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
@@ -123,7 +123,7 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
@@ -159,7 +159,7 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
@@ -201,7 +201,7 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
@@ -234,14 +234,15 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
- name: make
run: |
apt-get update && apt-get install -y make gcc g++
make CC=gcc REDIS_CFLAGS='-Werror -DREDIS_TEST -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3'
apt-get update && apt-get install -y make gcc
# Also enables jemalloc's sized deallocation checks to catch sdallocx()/zfree_with_size() misuse.
make CC=gcc REDIS_CFLAGS='-Werror -DREDIS_TEST -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3' JEMALLOC_CONFIGURE_OPTS='--enable-opt-size-checks'
- name: testprep
run: sudo apt-get install -y tcl8.6 tclx procps
- name: test
@@ -273,7 +274,7 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
@@ -307,7 +308,7 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
@@ -341,13 +342,13 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
- name: make
run: |
sudo apt-get update && sudo apt-get install libc6-dev-i386 g++ gcc-multilib g++-multilib
sudo apt-get update && sudo apt-get install libc6-dev-i386 gcc-multilib
make 32bit REDIS_CFLAGS='-Werror -DREDIS_TEST'
make -C tests/modules 32bit # the script below doesn't have an argument, we must build manually ahead of time
- name: testprep
@@ -381,7 +382,7 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
@@ -421,7 +422,7 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
@@ -461,7 +462,7 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
@@ -493,7 +494,7 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
@@ -571,7 +572,7 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
@@ -580,7 +581,7 @@ jobs:
- name: testprep
run: |
sudo apt-get update
sudo apt-get install tcl8.6 tclx valgrind g++ -y
sudo apt-get install tcl8.6 tclx valgrind -y
- name: test
if: true && !contains(github.event.inputs.skiptests, 'redis')
# Note that valgrind's overhead doesn't pair well with io-threads so we
@@ -604,7 +605,7 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
@@ -636,7 +637,7 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
@@ -645,7 +646,7 @@ jobs:
- name: testprep
run: |
sudo apt-get update
sudo apt-get install tcl8.6 tclx valgrind g++ -y
sudo apt-get install tcl8.6 tclx valgrind -y
- name: test
if: true && !contains(github.event.inputs.skiptests, 'redis')
run: ./runtest --valgrind --tags -iothreads --no-latency --verbose --clients 1 --timeout 2400 --dump-logs ${{github.event.inputs.test_args}}
@@ -666,7 +667,7 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
@@ -703,7 +704,7 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
@@ -744,7 +745,7 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
@@ -788,7 +789,7 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
@@ -833,7 +834,7 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
@@ -872,13 +873,13 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
- name: make
run: |
dnf -y install which gcc make g++
dnf -y install which gcc make
make REDIS_CFLAGS='-Werror'
- name: testprep
run: |
@@ -911,13 +912,13 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
- name: make
run: |
dnf -y install which gcc make openssl-devel openssl g++
dnf -y install which gcc make openssl-devel openssl
make BUILD_TLS=module REDIS_CFLAGS='-Werror'
- name: testprep
run: |
@@ -954,13 +955,13 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
- name: make
run: |
dnf -y install which gcc make openssl-devel openssl g++
dnf -y install which gcc make openssl-devel openssl
make BUILD_TLS=module REDIS_CFLAGS='-Werror'
- name: testprep
run: |
@@ -996,7 +997,7 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
@@ -1022,7 +1023,7 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
@@ -1048,7 +1049,7 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
@@ -1080,7 +1081,7 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
@@ -1093,21 +1094,18 @@ jobs:
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
!contains(github.event.inputs.skipjobs, 'freebsd')
timeout-minutes: 360
env:
CC: clang
CXX: clang++
steps:
- name: prep
if: github.event_name == 'workflow_dispatch'
run: |
echo "GITHUB_REPOSITORY=${{github.event.inputs.use_repo}}" >> $GITHUB_ENV
echo "GITHUB_HEAD_REF=${{github.event.inputs.use_git_ref}}" >> $GITHUB_ENV
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
- name: test
uses: cross-platform-actions/action@v0.30.0
uses: cross-platform-actions/action@v1.0.0
with:
operating_system: freebsd
environment_variables: MAKE
@@ -1134,7 +1132,7 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
@@ -1170,7 +1168,7 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
@@ -1206,7 +1204,7 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
@@ -1223,10 +1221,14 @@ jobs:
- name: cluster tests
if: true && !contains(github.event.inputs.skiptests, 'cluster')
run: ./runtest-cluster --log-req-res --dont-clean --force-resp3 ${{github.event.inputs.cluster_test_args}}
- name: Install Python dependencies
uses: py-actions/py-dependency-install@v4
- name: Set up Python
uses: actions/setup-python@v6
with:
path: "./utils/req-res-validator/requirements.txt"
python-version: "3.x"
cache: "pip"
cache-dependency-path: "./utils/req-res-validator/requirements.txt"
- name: Install Python dependencies
run: python -m pip install -r ./utils/req-res-validator/requirements.txt
- name: validator
run: ./utils/req-res-log-validator.py --verbose --fail-missing-reply-schemas ${{ (!contains(github.event.inputs.skiptests, 'redis') && !contains(github.event.inputs.skiptests, 'module') && !contains(github.event.inputs.sentinel, 'redis') && !contains(github.event.inputs.skiptests, 'cluster')) && github.event.inputs.test_args == '' && github.event.inputs.cluster_test_args == '' && '--fail-commands-not-all-hit' || '' }}
@@ -1247,7 +1249,7 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
@@ -1260,9 +1262,8 @@ jobs:
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
apt-get update
apt-get install -y make gcc-4.8 g++-4.8
apt-get install -y make gcc-4.8
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 100
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 100
make CC=gcc REDIS_CFLAGS='-Werror'
- name: testprep
run: apt-get install -y tcl tcltls tclx
@@ -1293,7 +1294,7 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
@@ -1306,10 +1307,9 @@ jobs:
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
apt-get update
apt-get install -y make gcc-4.8 g++-4.8 openssl libssl-dev
apt-get install -y make gcc-4.8 openssl libssl-dev
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 100
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 100
make CC=gcc CXX=g++ BUILD_TLS=module REDIS_CFLAGS='-Werror'
make CC=gcc BUILD_TLS=module REDIS_CFLAGS='-Werror'
- name: testprep
run: |
apt-get install -y tcl tcltls tclx
@@ -1344,7 +1344,7 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
@@ -1357,9 +1357,8 @@ jobs:
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
apt-get update
apt-get install -y make gcc-4.8 g++-4.8 openssl libssl-dev
apt-get install -y make gcc-4.8 openssl libssl-dev
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 100
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 100
make BUILD_TLS=module CC=gcc REDIS_CFLAGS='-Werror'
- name: testprep
run: |
@@ -1394,7 +1393,7 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
@@ -1422,7 +1421,7 @@ jobs:
echo "skiptests: ${{github.event.inputs.skiptests}}"
echo "test_args: ${{github.event.inputs.test_args}}"
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
repository: ${{ env.GITHUB_REPOSITORY }}
ref: ${{ env.GITHUB_HEAD_REF }}
+10 -6
View File
@@ -6,13 +6,17 @@ on:
schedule:
- cron: '0 0 * * *'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }}
cancel-in-progress: true
jobs:
test-external-standalone:
runs-on: ubuntu-latest
if: github.event_name != 'schedule' || github.repository == 'redis/redis'
timeout-minutes: 360
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Build
run: make REDIS_CFLAGS=-Werror
- name: Start redis-server
@@ -27,7 +31,7 @@ jobs:
--tags -slow
- name: Archive redis log
if: ${{ failure() }}
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: test-external-redis-log
path: external-redis.log
@@ -37,7 +41,7 @@ jobs:
if: github.event_name != 'schedule' || github.repository == 'redis/redis'
timeout-minutes: 360
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Build
run: make REDIS_CFLAGS=-Werror
- name: Start redis-server
@@ -55,7 +59,7 @@ jobs:
--tags -slow
- name: Archive redis log
if: ${{ failure() }}
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: test-external-cluster-log
path: external-redis-cluster.log
@@ -65,7 +69,7 @@ jobs:
if: github.event_name != 'schedule' || github.repository == 'redis/redis'
timeout-minutes: 360
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Build
run: make REDIS_CFLAGS=-Werror
- name: Start redis-server
@@ -79,7 +83,7 @@ jobs:
--tags "-slow -needs:debug"
- name: Archive redis log
if: ${{ failure() }}
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: test-external-redis-nodebug-log
path: external-redis-nodebug.log
@@ -1,155 +0,0 @@
name: Post-Release Automation
on:
release:
types: [published]
jobs:
automate-release-scripts:
# Only run for the main redis/redis repository (not forks)
# Note: Only users with write access can publish releases, providing implicit authorization
if: github.repository == 'redis/redis'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0 # Fetch all history for git archive
- name: Extract and validate release information
id: release-info
env:
TAG_NAME: ${{ github.event.release.tag_name }}
GH_TOKEN: ${{ github.token }}
run: |
# Extract tag name from the release event (via env var to prevent injection)
echo "tag_name=${TAG_NAME}" >> $GITHUB_OUTPUT
echo "Release tag: ${TAG_NAME}"
# Get the latest release tag
LATEST_TAG=$(gh release view --json tagName --jq '.tagName')
echo "Latest release tag from gh cli: ${LATEST_TAG}"
# Determine release type by comparing with latest release
if [[ "${TAG_NAME}" == "${LATEST_TAG}" ]]; then
echo "release_type=latest" >> $GITHUB_OUTPUT
echo "Detected latest release: ${TAG_NAME}"
else
echo "release_type=non-latest" >> $GITHUB_OUTPUT
echo "Detected non-latest release: ${TAG_NAME} (latest is ${LATEST_TAG})"
fi
- name: Set up environment variables
run: |
echo "RELEASE_TAG=${{ steps.release-info.outputs.tag_name }}" >> $GITHUB_ENV
echo "RELEASE_TYPE=${{ steps.release-info.outputs.release_type }}" >> $GITHUB_ENV
echo "Environment variables set:"
echo " RELEASE_TAG: ${{ steps.release-info.outputs.tag_name }}"
echo " RELEASE_TYPE: ${{ steps.release-info.outputs.release_type }}"
- name: Create tarball
id: create-tarball
run: |
echo "Creating tarball for version ${RELEASE_TAG}..."
# TODO: Implement tarball creation using utils/releasetools/01_create_tarball.sh
# ./utils/releasetools/01_create_tarball.sh ${RELEASE_TAG}
# Placeholder: Verify tarball was created
# TARBALL_PATH="/tmp/redis-${RELEASE_TAG}.tar.gz"
# if [ ! -f "${TARBALL_PATH}" ]; then
# echo "Error: Tarball not found at ${TARBALL_PATH}"
# exit 1
# fi
# echo "tarball_path=${TARBALL_PATH}" >> $GITHUB_OUTPUT
echo "✓ Tarball creation step (placeholder)"
- name: Upload tarball
id: upload-tarball
run: |
echo "Uploading tarball for version ${RELEASE_TAG}..."
# TODO: Implement tarball upload
# This will require:
# - SSH credentials/keys for upload to download.redis.io
# - Adaptation of utils/releasetools/02_upload_tarball.sh for CI environment
echo "✓ Tarball upload step (placeholder)"
- name: Test release tarball
id: test-release
run: |
echo "Testing release tarball for version ${RELEASE_TAG}..."
# TODO: Implement release testing using utils/releasetools/03_test_release.sh
# This will:
# - Download the uploaded tarball
# - Extract and build Redis
echo "✓ Release testing step (placeholder)"
- name: Update release hashes
id: update-hashes
run: |
echo "Updating release hashes for version ${RELEASE_TAG}..."
# TODO: Implement hash update using utils/releasetools/04_release_hash.sh
# This will require:
# - Access to redis-hashes repository
# - Git credentials for committing and pushing
echo "✓ Release hashes update step (placeholder)"
- name: Approval gate for latest releases
if: steps.release-info.outputs.release_type == 'latest'
run: |
echo "Latest release detected. Manual approval required for production deployment."
# TODO: Implement approval workflow
# This could use GitHub Environments with required reviewers
# or a manual approval step
echo "✓ Approval gate (placeholder)"
- name: Update stable symlink (latest releases only)
if: steps.release-info.outputs.release_type == 'latest'
id: update-stable
run: |
echo "This is a latest release. Updating stable symlink after approval."
# TODO: Implement stable symlink update
# This step should only run for latest releases (not non-latest)
# It will update the redis-stable symlink on download.redis.io
# This is part of the upload script (02_upload_tarball.sh)
echo "✓ Stable symlink update step (placeholder)"
- name: Summary
if: always()
run: |
echo "## Post-Release Automation Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Release Tag:** ${{ steps.release-info.outputs.tag_name }}" >> $GITHUB_STEP_SUMMARY
echo "- **Release Type:** ${{ steps.release-info.outputs.release_type }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Steps Status" >> $GITHUB_STEP_SUMMARY
echo "- Create tarball: ${{ steps.create-tarball.outcome }}" >> $GITHUB_STEP_SUMMARY
echo "- Upload tarball: ${{ steps.upload-tarball.outcome }}" >> $GITHUB_STEP_SUMMARY
echo "- Test release: ${{ steps.test-release.outcome }}" >> $GITHUB_STEP_SUMMARY
echo "- Update hashes: ${{ steps.update-hashes.outcome }}" >> $GITHUB_STEP_SUMMARY
if [[ "${{ steps.release-info.outputs.release_type }}" == "latest" ]]; then
echo "- Update stable symlink: ${{ steps.update-stable.outcome }}" >> $GITHUB_STEP_SUMMARY
fi
- name: Send Slack notification
if: always()
run: |
echo "Sending Slack notification for release ${RELEASE_TAG}..."
# TODO: Implement Slack notification
# This will require:
# - Slack webhook URL or bot token (stored in secrets)
# - Determine appropriate channel (e.g., #releases, #redis-releases)
# - Craft message with release information and workflow status
# Example using webhook:
# curl -X POST -H 'Content-type: application/json' \
# --data '{"channel":"#releases","text":"Release ${RELEASE_TAG} automation completed"}' \
# ${{ secrets.SLACK_WEBHOOK_URL }}
echo "✓ Slack notification step (placeholder)"
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@v1
uses: actions/create-github-app-token@v3
with:
app-id: ${{ secrets.DOCS_APP_ID }}
private-key: ${{ secrets.DOCS_APP_PRIVATE_KEY }}
+6 -2
View File
@@ -8,13 +8,17 @@ on:
paths:
- 'src/commands/*.json'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }}
cancel-in-progress: true
jobs:
reply-schemas-linter:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup nodejs
uses: actions/setup-node@v4
uses: actions/setup-node@v6
- name: Install packages
run: npm install ajv
- name: linter
+6 -2
View File
@@ -9,6 +9,10 @@ on:
push:
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }}
cancel-in-progress: true
jobs:
build:
name: Spellcheck
@@ -16,10 +20,10 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: pip cache
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
+1 -1
View File
@@ -30,7 +30,7 @@ deps/lua/src/luac
deps/lua/src/liblua.a
deps/hdr_histogram/libhdrhistogram.a
deps/fpconv/libfpconv.a
deps/fast_float/libfast_float.a
deps/tre/libtre.a
tests/tls/*
.make-*
.prerequisites
+10 -10
View File
@@ -53,7 +53,7 @@ Redis excels in various applications, including:
- **Distributed Session Store:** Offers flexible session data modeling (string, JSON, hash).
- **Data Structure Server:** Provides low-level data structures (strings, lists, sets, hashes, sorted sets, JSON, etc.) with high-level semantics (counters, queues, leaderboards, rate limiters) and supports transactions & scripting.
- **NoSQL Data Store:** Key-value, document, and time series data storage.
- **Search and Query Engine:** Indexing for hash/JSON documents, supporting vector search, full-text search, geospatial queries, ranking, and aggregations via Redis Query Engine.
- **Search and Query Engine:** Indexing for hash/JSON documents, supporting vector search, full-text search, geospatial queries, ranking, and aggregations via Redis Search.
- **Event Store & Message Broker:** Implements queues (lists), priority queues (sorted sets), event deduplication (sets), streams, and pub/sub with probabilistic stream processing capabilities.
- **Vector Store for GenAI:** Integrates with AI applications (e.g. LangGraph, mem0) for short-term memory, long-term memory, LLM response caching (semantic caching), and retrieval augmented generation (RAG).
- **Real-Time Analytics:** Powers personalization, recommendations, fraud detection, and risk assessment.
@@ -172,9 +172,10 @@ Redis provides a variety of data types, processing engines, and capabilities to
**Important:** Features marked with an asterisk (\*) require Redis to be compiled with the `BUILD_WITH_MODULES=yes` flag when [building Redis from source](#build-redis-from-source)
- [**String:**](https://redis.io/docs/latest/develop/data-types/strings) Sequences of bytes, including text, serialized objects, and binary arrays used for caching, counters, and bitwise operations.
- [**JSON:**](https://redis.io/docs/latest/develop/data-types/json/) Nested JSON documents that are indexed and searchable using JSONPath expressions and with [Redis Query Engine](https://redis.io/docs/latest/develop/interact/search-and-query/)
- [**JSON:**](https://redis.io/docs/latest/develop/data-types/json/) Nested JSON documents that are indexed and searchable using JSONPath expressions and with [Redis Search](https://redis.io/docs/latest/develop/ai/search-and-query/)
- [**Array:**](https://redis.io/docs/latest/develop/data-types/arrays/) Sparse, index-addressable collection of string values
- [**Hash:**](https://redis.io/docs/latest/develop/data-types/hashes/) Field-value maps used to represent basic objects and store groupings of key-value pairs with support for [hash field expiration (TTL)](https://redis.io/docs/latest/develop/data-types/hashes/#field-expiration)
- [**Redis Query Engine:**](https://redis.io/docs/latest/develop/interact/search-and-query/) Use Redis as a document database, a vector database, a secondary index, and a search engine. Define indexes for hash and JSON documents and then use a rich query language for vector search, full-text search, geospatial queries, and aggregations.
- [**Redis Search:**](https://redis.io/docs/latest/develop/ai/search-and-query/) Use Redis as a document database, a vector database, a secondary index, and a search engine. Define indexes for hash and JSON documents and then use a rich query language for vector search, full-text search, geospatial queries, and aggregations.
- [**List:**](https://redis.io/docs/latest/develop/data-types/lists/) Linked lists of string values used as stacks, queues, and for queue management.
- [**Set:**](https://redis.io/docs/latest/develop/data-types/sets/) Unordered collection of unique strings used for tracking unique items, relations, and common set operations (intersections, unions, differences).
- [**Sorted set:**](https://redis.io/docs/latest/develop/data-types/sorted-sets/) Collection of unique strings ordered by an associated score used for leaderboards and rate limiters.
@@ -272,7 +273,7 @@ Tested with the following Docker image:
```sh
cd /usr/src/redis-<version>
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes DISABLE_WERRORS=yes
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes
make -j "$(nproc)" all
```
@@ -338,7 +339,7 @@ Tested with the following Docker image:
```sh
cd /usr/src/redis-<version>
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes DISABLE_WERRORS=yes
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes
make -j "$(nproc)" all
```
@@ -392,7 +393,7 @@ Tested with the following Docker image:
```sh
cd /usr/src/redis-<version>
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes DISABLE_WERRORS=yes
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes
make -j "$(nproc)" all
```
@@ -449,7 +450,7 @@ Tested with the following Docker images:
```sh
cd /usr/src/redis-<version>
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes DISABLE_WERRORS=yes
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes
make -j "$(nproc)" all
```
@@ -568,7 +569,7 @@ Tested with the following Docker images:
```sh
source /etc/profile.d/gcc-toolset-13.sh
cd /usr/src/redis-<version>
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes DISABLE_WERRORS=yes
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes
make -j "$(nproc)" all
```
@@ -685,7 +686,7 @@ Tested with the following Docker images:
```sh
source /etc/profile.d/gcc-toolset-13.sh
cd /usr/src/redis-<version>
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes DISABLE_WERRORS=yes
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes
make -j "$(nproc)" all
```
@@ -757,7 +758,6 @@ Tested with the following Docker images:
export HOMEBREW_PREFIX="$(brew --prefix)"
export BUILD_WITH_MODULES=yes
export BUILD_TLS=yes
export DISABLE_WERRORS=yes
PATH="$HOMEBREW_PREFIX/opt/libtool/libexec/gnubin:$HOMEBREW_PREFIX/opt/llvm@18/bin:$HOMEBREW_PREFIX/opt/make/libexec/gnubin:$HOMEBREW_PREFIX/opt/gnu-sed/libexec/gnubin:$HOMEBREW_PREFIX/opt/coreutils/libexec/gnubin:$PATH"
export LDFLAGS="-L$HOMEBREW_PREFIX/opt/llvm@18/lib"
export CPPFLAGS="-I$HOMEBREW_PREFIX/opt/llvm@18/include"
+14 -4
View File
@@ -23,10 +23,20 @@ unless this is not possible or feasible with a reasonable effort.
## Reporting a Vulnerability
If you believe you've discovered a serious vulnerability, please contact the
Redis core team at redis@redis.io. We will evaluate your report and if
necessary issue a fix and an advisory. If the issue was previously undisclosed,
we'll also mention your name in the credits.
If you believe you have found a security vulnerability, to ensure proper review
and assessment, we kindly ask vulnerability reports be submitted through
our [Redis Vulnerability Disclosure Program.](https://redis.io/redis-responsible-vulnerability-disclosure/)
We have found this path to be beneficial for both researchers and us for
a number of reasons. Including, offering fast response times to researchers and
opportunities for us to invite those with exceptional reports into closed paid
engagements.
For those averse to using our chosen platform, we will also accept reports directly
via GitHub's "Report a Vulnerability".
To contact the security team directly with questions use: [security@redis.com](mailto:security@redis.com)
## Responsible Disclosure
+7 -6
View File
@@ -59,7 +59,7 @@ distclean:
-(cd jemalloc && [ -f Makefile ] && $(MAKE) distclean) > /dev/null || true
-(cd hdr_histogram && $(MAKE) clean) > /dev/null || true
-(cd fpconv && $(MAKE) clean) > /dev/null || true
-(cd fast_float && $(MAKE) clean) > /dev/null || true
-(cd tre && $(MAKE) clean) > /dev/null || true
-(cd xxhash && $(MAKE) clean) > /dev/null || true
-(rm -f .make-*)
@@ -95,11 +95,12 @@ fpconv: .make-prerequisites
.PHONY: fpconv
fast_float: .make-prerequisites
tre: .make-prerequisites
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
cd fast_float && $(MAKE) libfast_float CFLAGS="$(DEPS_CFLAGS)" LDFLAGS="$(DEPS_LDFLAGS)"
cd tre && $(MAKE) CFLAGS="$(DEPS_CFLAGS)" LDFLAGS="$(DEPS_LDFLAGS)"
.PHONY: tre
.PHONY: fast_float
XXHASH_CFLAGS = -fPIC $(DEPS_CFLAGS)
xxhash: .make-prerequisites
@@ -136,8 +137,8 @@ lua: .make-prerequisites
.PHONY: lua
JEMALLOC_CFLAGS=$(CFLAGS)
JEMALLOC_LDFLAGS=$(LDFLAGS)
JEMALLOC_CFLAGS=$(ENABLE_LTO) $(CFLAGS)
JEMALLOC_LDFLAGS=$(ENABLE_LTO) $(LDFLAGS)
ifneq ($(DEB_HOST_GNU_TYPE),)
JEMALLOC_CONFIGURE_OPTS += --host=$(DEB_HOST_GNU_TYPE)
-27
View File
@@ -1,27 +0,0 @@
# Fallback to gcc/g++ when $CC or $CXX is not in $PATH.
CC ?= gcc
CXX ?= g++
WARN=-Wall
OPT=-O3
STD=-std=c++11
DEFS=-DFASTFLOAT_ALLOWS_LEADING_PLUS
FASTFLOAT_CFLAGS=$(WARN) $(OPT) $(STD) $(DEFS) $(CFLAGS)
FASTFLOAT_LDFLAGS=$(LDFLAGS)
libfast_float: fast_float_strtod.o
$(AR) -r libfast_float.a fast_float_strtod.o
32bit: FASTFLOAT_CFLAGS += -m32
32bit: FASTFLOAT_LDFLAGS += -m32
32bit: libfast_float
fast_float_strtod.o: fast_float_strtod.cpp
$(CXX) $(FASTFLOAT_CFLAGS) -c fast_float_strtod.cpp $(FASTFLOAT_LDFLAGS)
clean:
rm -f *.o
rm -f *.a
rm -f *.h.gch
rm -rf *.dSYM
-21
View File
@@ -1,21 +0,0 @@
README for fast_float v6.1.4
----------------------------------------------
We're using the fast_float library[1] in our (compiled-in)
floating-point fast_float_strtod implementation for faster and more
portable parsing of 64 decimal strings.
The single file fast_float.h is an amalgamation of the entire library,
which can be (re)generated with the amalgamate.py script (from the
fast_float repository) via the command
```
git clone https://github.com/fastfloat/fast_float
cd fast_float
git checkout v6.1.4
python3 ./script/amalgamate.py --license=MIT \
> $REDIS_SRC/deps/fast_float/fast_float.h
```
[1]: https://github.com/fastfloat/fast_float
-3838
View File
File diff suppressed because it is too large Load Diff
-32
View File
@@ -1,32 +0,0 @@
#include "fast_float.h"
#include <iostream>
#include <string>
#include <system_error>
#include <cerrno>
/* Convert NPTR to a double using the fast_float library.
*
* This function behaves similarly to the standard strtod function, converting
* the initial portion of the string pointed to by `nptr` to a `double` value,
* using the fast_float library for high performance. If the conversion fails,
* errno is set to EINVAL error code.
*
* @param nptr A pointer to the null-terminated byte string to be interpreted.
* @param endptr A pointer to a pointer to character. If `endptr` is not NULL,
* it will point to the character after the last character used
* in the conversion.
* @return The converted value as a double. If no valid conversion could
* be performed, returns 0.0.
* If ENDPTR is not NULL, a pointer to the character after the last one used
* in the number is put in *ENDPTR. */
extern "C" double fast_float_strtod(const char *nptr, char **endptr) {
double result = 0.0;
auto answer = fast_float::from_chars(nptr, nptr + strlen(nptr), result);
if (answer.ec != std::errc()) {
errno = EINVAL; // Fallback to for other errors
}
if (endptr != NULL) {
*endptr = (char *)answer.ptr;
}
return result;
}
-15
View File
@@ -1,15 +0,0 @@
#ifndef __FAST_FLOAT_STRTOD_H__
#define __FAST_FLOAT_STRTOD_H__
#if defined(__cplusplus)
extern "C"
{
#endif
double fast_float_strtod(const char *in, char **out);
#if defined(__cplusplus)
}
#endif
#endif /* __FAST_FLOAT_STRTOD_H__ */
+29
View File
@@ -0,0 +1,29 @@
This is the license, copyright notice, and disclaimer for TRE, a regex
matching package (library and tools) with support for approximate
matching.
Copyright (c) 2001-2009 Ville Laurikari <vl@iki.fi>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. 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 HOLDER 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.
+79
View File
@@ -0,0 +1,79 @@
STD= -std=c99
WARN= -Wall
OPT= -Os
ifeq ($(SANITIZER),address)
CFLAGS+=-fsanitize=address -fno-sanitize-recover=all -fno-omit-frame-pointer
LDFLAGS+=-fsanitize=address
else
ifeq ($(SANITIZER),undefined)
CFLAGS+=-fsanitize=undefined -fno-sanitize-recover=all -fno-omit-frame-pointer
LDFLAGS+=-fsanitize=undefined
else
ifeq ($(SANITIZER),thread)
CFLAGS+=-fsanitize=thread -fno-sanitize-recover=all -fno-omit-frame-pointer
LDFLAGS+=-fsanitize=thread
else
ifeq ($(SANITIZER),memory)
CFLAGS+=-fsanitize=memory -fsanitize-memory-track-origins=2 -fno-sanitize-recover=all -fno-omit-frame-pointer
LDFLAGS+=-fsanitize=memory
endif
endif
endif
endif
R_CFLAGS= $(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) -DTRE_REGEX_T_FIELD=value -Ilocal_includes -Ilib
R_LDFLAGS= $(LDFLAGS)
DEBUG= -g
R_CC=$(CC) $(R_CFLAGS)
R_LD=$(CC) $(R_LDFLAGS)
AR= ar
ARFLAGS= rcs
TRE_OBJ=lib/regcomp.o lib/regerror.o lib/regexec.o lib/tre-ast.o lib/tre-compile.o \
lib/tre-filter.o lib/tre-match-backtrack.o lib/tre-match-parallel.o \
lib/tre-mem.o lib/tre-parse.o lib/tre-stack.o lib/xmalloc.o
TRE_TESTS=tests/retest tests/test-str-source tests/test-literal-opt tests/test-malformed-regn
libtre.a: $(TRE_OBJ)
$(AR) $(ARFLAGS) $@ $+
check: $(TRE_TESTS)
@set -e; \
for test in $(TRE_TESTS); do \
echo "TEST $$test"; \
./$$test; \
done
tests/retest: tests/retest.c libtre.a
$(R_LD) $(R_CFLAGS) -DHAVE_REGNEXEC -DHAVE_REGNCOMP -o $@ $< libtre.a
tests/test-str-source: tests/test-str-source.c libtre.a
$(R_LD) $(R_CFLAGS) -o $@ $< libtre.a
tests/test-literal-opt: tests/test-literal-opt.c libtre.a
$(R_LD) $(R_CFLAGS) -o $@ $< libtre.a
tests/test-malformed-regn: tests/test-malformed-regn.c libtre.a
$(R_LD) $(R_CFLAGS) -o $@ $< libtre.a
lib/regcomp.o: lib/regcomp.c local_includes/tre.h local_includes/tre-config.h lib/tre-internal.h lib/xmalloc.h
lib/regerror.o: lib/regerror.c local_includes/tre.h
lib/regexec.o: lib/regexec.c local_includes/tre.h lib/tre-internal.h lib/xmalloc.h
lib/tre-ast.o: lib/tre-ast.c lib/tre-ast.h lib/tre-internal.h
lib/tre-compile.o: lib/tre-compile.c lib/tre-compile.h lib/tre-internal.h lib/tre-mem.h lib/tre-parse.h lib/tre-stack.h lib/xmalloc.h
lib/tre-filter.o: lib/tre-filter.c lib/tre-filter.h lib/tre-internal.h
lib/tre-match-backtrack.o: lib/tre-match-backtrack.c lib/tre-internal.h lib/tre-match-utils.h lib/tre-mem.h lib/tre-stack.h
lib/tre-match-parallel.o: lib/tre-match-parallel.c lib/tre-internal.h lib/tre-match-utils.h lib/tre-mem.h
lib/tre-mem.o: lib/tre-mem.c lib/tre-mem.h
lib/tre-parse.o: lib/tre-parse.c lib/tre-ast.h lib/tre-compile.h lib/tre-filter.h lib/tre-internal.h lib/tre-mem.h lib/tre-parse.h lib/tre-stack.h lib/xmalloc.h
lib/tre-stack.o: lib/tre-stack.c lib/tre-internal.h lib/tre-stack.h
lib/xmalloc.o: lib/xmalloc.c lib/xmalloc.h
.c.o:
$(R_CC) -c -o $@ $<
clean:
rm -f $(TRE_OBJ) libtre.a $(TRE_TESTS)
+276
View File
@@ -0,0 +1,276 @@
Introduction
============
TRE is a lightweight, robust, and efficient POSIX compliant regexp
matching library with some exciting features such as approximate
(fuzzy) matching.
The matching algorithm used in TRE uses linear worst-case time in
the length of the text being searched, and quadratic worst-case
time in the length of the used regular expression.
In other words, the time complexity of the algorithm is O(M^2N), where
M is the length of the regular expression and N is the length of the
text. The used space is also quadratic on the length of the regex,
but does not depend on the searched string. This quadratic behaviour
occurs only on pathological cases which are probably very rare in
practice.
Hacking
=======
Here's how to work with this code.
Prerequisites
-------------
You will need the following tools installed on your system:
- autoconf
- automake
- gettext (including autopoint)
- libtool
- zip (optional)
Building
--------
First, prepare the tree. Change to the root of the source directory
and run
./utils/autogen.sh
This will regenerate various things using the prerequisite tools so
that you end up with a buildable tree.
After this, you can run the configure script and build TRE as usual:
./configure
make
make check
make install
Building a source code package
------------------------------
In a prepared tree, this command creates a source code tarball:
./configure && make dist
Alternatively, you can run
./utils/build-sources.sh
which builds the source code packages and puts them in the `dist`
subdirectory. This script needs a working `zip` command.
Features
========
TRE is not just yet another regexp matcher. TRE has some features
which are not there in most free POSIX compatible implementations.
Most of these features are not present in non-free implementations
either, for that matter.
Approximate matching
--------------------
Approximate pattern matching allows matches to be approximate, that
is, allows the matches to be close to the searched pattern under some
measure of closeness. TRE uses the edit-distance measure (also known
as the Levenshtein distance) where characters can be inserted,
deleted, or substituted in the searched text in order to get an exact
match.
Each insertion, deletion, or substitution adds the distance, or cost,
of the match. TRE can report the matches which have a cost lower than
some given threshold value. TRE can also be used to search for
matches with the lowest cost.
TRE includes a version of the agrep (approximate grep) command line
tool for approximate regexp matching in the style of grep. Unlike
other agrep implementations (like the one by Sun Wu and Udi Manber
from University of Arizona) TRE agrep allows full regexps of any
length, any number of errors, and non-uniform costs for insertion,
deletion and substitution.
Strict standard conformance
---------------------------
POSIX defines the behaviour of regexp functions precisely. TRE
attempts to conform to these specifications as strictly as possible.
TRE always returns the correct matches for subpatterns, for example.
Very few other implementations do this correctly. In fact, the only
other implementations besides TRE that I am aware of (free or not)
that get it right are Rx by Tom Lord, Regex++ by John Maddock, and the
AT&T ast regex by Glenn Fowler and Doug McIlroy.
The standard TRE tries to conform to is the IEEE Std 1003.1-2001, or
Open Group Base Specifications Issue 6, commonly referred to as
“POSIX”. The relevant parts are the base specifications on regular
expressions (and the rationale) and the description of the `regcomp()`
API.
For an excellent survey on POSIX regexp matchers, see the testregex
pages by Glenn Fowler of AT&T Labs Research.
Predictable matching speed
--------------------------
Because of the matching algorithm used in TRE, the maximum time
consumed by any `regexec()` call is always directly proportional to
the length of the searched string. There is one exception: if back
references are used, the matching may take time that grows
exponentially with the length of the string. This is because matching
back references is an NP complete problem, and almost certainly
requires exponential time to match in the worst case.
Predictable and modest memory consumption
-----------------------------------------
A `regexec()` call never allocates memory from the heap. TRE allocates
all the memory it needs during a `regcomp()` call, and some temporary
working space from the stack frame for the duration of the `regexec()`
call. The amount of temporary space needed is constant during
matching and does not depend on the searched string. For regexps of
reasonable size TRE needs less than 50K of dynamically allocated
memory during the `regcomp()` call, less than 20K for the compiled
pattern buffer, and less than two kilobytes of temporary working space
from the stack frame during a `regexec()` call. There is no time /
memory tradeoff. TRE is also small in code size; statically linking
with TRE increases the executable size less than 30K (gcc-3.2, x86,
GNU/Linux).
Wide character and multibyte character set support
--------------------------------------------------
TRE supports multibyte character sets. This makes it possible to use
regexps seamlessly with, for example, Japanese locales. TRE also
provides a wide character API.
Binary pattern and data support
-------------------------------
TRE provides APIs which allow binary zero characters both in regexps
and searched strings. The standard API cannot be easily used to, for
example, search for printable words from binary data (although it is
possible with some hacking). Searching for patterns which contain
binary zeroes embedded is not possible at all with the standard API.
Completely thread safe
----------------------
TRE is completely thread safe. All the exported functions are
re-entrant, and a single compiled regexp object can be used
simultaneously in multiple contexts; e.g. in `main()` and a signal
handler, or in many threads of a multithreaded application.
Portable
--------
TRE is portable across multiple platforms. Below is a table of
platforms and compilers used to develop and test TRE:
<table>
<tr><th>Platform</th> <th>Compiler</th></tr>
<tr><td>FreeBSD 14.1</td> <td>Clang 18</td></tr>
<tr><td>Ubuntu 22.04</td> <td>GCC 11</td></tr>
<tr><td>macOS 14.6</td> <td>Clang 14</td></tr>
<tr><td>Windows 11</td> <td>Microsoft Visual Studio 2022</td></tr>
</table>
TRE should compile without changes on most modern POSIX-like
platforms, and be easily portable to any platform with a hosted C
implementation.
Depending on the platform, you may need to install libutf8 to get
wide character and multibyte character set support.
Free
----
TRE is released under a license which is essentially the same as the
“2 clause” BSD-style license used in NetBSD. See the file LICENSE for
details.
Roadmap
-------
There are currently two features, both related to collating elements,
missing from 100% POSIX compliance. These are:
* Support for collating elements (e.g. `[[.\<X>.]]`, where `\<X>` is a
collating element). It is not possible to support multi-character
collating elements portably, since POSIX does not define a way to
determine whether a character sequence is a multi-character
collating element or not.
* Support for equivalence classes, for example `[[=\<X>=]]`, where
`\<X>` is a collating element. An equivalence class matches any
character which has the same primary collation weight as `\<X>`.
Again, POSIX provides no portable mechanism for determining the
primary collation weight of a collating element.
Note that other portable regexp implementations don't support
collating elements either. The single exception is Regex++, which
comes with its own database for collating elements for different
locales. Support for collating elements and equivalence classes has
not been widely requested and is not very high on the TODO list at the
moment.
These are other features I'm planning to implement real soon now:
* All the missing GNU extensions enabled in GNU regex, such as
`[[:<:]]` and `[[:>:]]`.
* A `REG_SHORTEST` `regexec()` flag for returning the shortest match
instead of the longest match.
* Perl-compatible syntax:
* `[:^class:]`
Matches anything but the characters in class. Note that
`[^[:class:]]` works already, this would be just a convenience
shorthand.
* `\A`
Match only at beginning of string.
* `\Z`
Match only at end of string, or before newline at the end.
* `\z`
Match only at end of string.
* `\l`
Lowercase next char (think vi).
* `\u`
Uppercase next char (think vi).
* `\L`
Lowercase till `\E` (think vi).
* `\U`
Uppercase till `\E` (think vi).
* `(?=pattern)`
Zero-width positive look-ahead assertions.
* `(?!pattern)`
Zero-width negative look-ahead assertions.
* `(?<=pattern)`
Zero-width positive look-behind assertions.
* `(?<!pattern)`
Zero-width negative look-behind assertions.
Documentation especially for the nonstandard features of TRE, such as
approximate matching, is a work in progress (with “progress” loosely
defined...) If you want to find an extension to use, reading the
`include/tre/tre.h` header might provide some additional hints if you
are comfortable with C source code.
+188
View File
@@ -0,0 +1,188 @@
/*
tre_regcomp.c - TRE POSIX compatible regex compilation functions.
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include "tre-internal.h"
#include "xmalloc.h"
int
tre_regncomp(regex_t *preg, const char *regex, size_t n, int cflags)
{
int ret;
if (n > TRE_MAX_RE)
return REG_ESPACE;
#if TRE_WCHAR
tre_char_t *wregex;
size_t wlen;
wregex = xmalloc(sizeof(tre_char_t) * (n + 1));
if (wregex == NULL)
return REG_ESPACE;
/* If the current locale uses the standard single byte encoding of
characters, we don't do a multibyte string conversion. If we did,
many applications which use the default locale would break since
the default "C" locale uses the 7-bit ASCII character set, and
all characters with the eighth bit set would be considered invalid. */
#if TRE_MULTIBYTE
if (TRE_MB_CUR_MAX == 1)
#endif /* TRE_MULTIBYTE */
{
size_t i;
const unsigned char *str = (const unsigned char *)regex;
tre_char_t *wstr = wregex;
for (i = 0; i < n; i++)
*(wstr++) = *(str++);
wlen = n;
}
#if TRE_MULTIBYTE
else
{
size_t consumed;
tre_char_t *wcptr = wregex;
#ifdef HAVE_MBSTATE_T
mbstate_t state;
memset(&state, '\0', sizeof(state));
#endif /* HAVE_MBSTATE_T */
while (n > 0)
{
consumed = tre_mbrtowc(wcptr, regex, n, &state);
switch (consumed)
{
case 0:
if (*regex == '\0')
consumed = 1;
else
{
xfree(wregex);
return REG_BADPAT;
}
break;
case -1:
DPRINT(("mbrtowc: error %d: %s.\n", errno, strerror(errno)));
xfree(wregex);
return REG_BADPAT;
case -2:
/* The last character wasn't complete. Let's not call it a
fatal error. */
consumed = n;
break;
}
regex += consumed;
n -= consumed;
wcptr++;
}
wlen = wcptr - wregex;
}
#endif /* TRE_MULTIBYTE */
wregex[wlen] = L'\0';
ret = tre_compile(preg, wregex, wlen, cflags);
xfree(wregex);
#else /* !TRE_WCHAR */
ret = tre_compile(preg, (const tre_char_t *)regex, n, cflags);
#endif /* !TRE_WCHAR */
return ret;
}
/* this version takes bytes literally, to be used with raw vectors */
int
tre_regncompb(regex_t *preg, const char *regex, size_t n, int cflags)
{
int ret;
if (n > TRE_MAX_RE)
return REG_ESPACE;
#if TRE_WCHAR /* wide chars = we need to convert it all to the wide format */
tre_char_t *wregex;
size_t i;
wregex = xmalloc(sizeof(tre_char_t) * n);
if (wregex == NULL)
return REG_ESPACE;
for (i = 0; i < n; i++)
wregex[i] = (tre_char_t) ((unsigned char) regex[i]);
ret = tre_compile(preg, wregex, n, cflags | REG_USEBYTES);
xfree(wregex);
#else /* !TRE_WCHAR */
ret = tre_compile(preg, (const tre_char_t *)regex, n, cflags | REG_USEBYTES);
#endif /* !TRE_WCHAR */
return ret;
}
int
tre_regcomp(regex_t *preg, const char *regex, int cflags)
{
size_t n = regex ? strlen(regex) : 0;
if (n > TRE_MAX_RE)
return REG_ESPACE;
return tre_regncomp(preg, regex, n, cflags);
}
int
tre_regcompb(regex_t *preg, const char *regex, int cflags)
{
int ret;
tre_char_t *wregex;
size_t i, n = regex ? strlen(regex) : 0;
const unsigned char *str = (const unsigned char *)regex;
tre_char_t *wstr;
if (n > TRE_MAX_RE)
return REG_ESPACE;
wregex = xmalloc(sizeof(tre_char_t) * (n + 1));
if (wregex == NULL) return REG_ESPACE;
wstr = wregex;
for (i = 0; i < n; i++)
*(wstr++) = *(str++);
wregex[n] = L'\0';
ret = tre_compile(preg, wregex, n, cflags | REG_USEBYTES);
xfree(wregex);
return ret;
}
#ifdef TRE_WCHAR
int
tre_regwncomp(regex_t *preg, const wchar_t *regex, size_t n, int cflags)
{
if (n > TRE_MAX_RE)
return REG_ESPACE;
return tre_compile(preg, regex, n, cflags);
}
int
tre_regwcomp(regex_t *preg, const wchar_t *regex, int cflags)
{
size_t n = regex ? wcslen(regex) : 0;
if (n > TRE_MAX_RE)
return REG_ESPACE;
return tre_compile(preg, regex, n, cflags);
}
#endif /* TRE_WCHAR */
void
tre_regfree(regex_t *preg)
{
tre_free(preg);
}
/* EOF */
+86
View File
@@ -0,0 +1,86 @@
/*
tre_regerror.c - POSIX tre_regerror() implementation for TRE.
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */
#include <string.h>
#ifdef HAVE_WCHAR_H
#include <wchar.h>
#endif /* HAVE_WCHAR_H */
#ifdef HAVE_WCTYPE_H
#include <wctype.h>
#endif /* HAVE_WCTYPE_H */
#include "tre-internal.h"
#ifdef HAVE_GETTEXT
#include <libintl.h>
#else
#define dgettext(p, s) s
#define gettext(s) s
#endif
#define _(String) dgettext(PACKAGE, String)
#define gettext_noop(String) String
#define xstr(s) str(s)
#define str(s) #s
/* Error message strings for error codes listed in `tre.h'. This list
needs to be in sync with the codes listed there, naturally. */
static const char *tre_error_messages[] =
{ gettext_noop("No error"), /* REG_OK */
gettext_noop("No match"), /* REG_NOMATCH */
gettext_noop("Invalid regexp"), /* REG_BADPAT */
gettext_noop("Unknown collating element"), /* REG_ECOLLATE */
gettext_noop("Unknown character class name"), /* REG_ECTYPE */
gettext_noop("Trailing backslash"), /* REG_EESCAPE */
gettext_noop("Invalid back reference"), /* REG_ESUBREG */
gettext_noop("Missing ']'"), /* REG_EBRACK */
gettext_noop("Missing ')'"), /* REG_EPAREN */
gettext_noop("Missing '}'"), /* REG_EBRACE */
gettext_noop("Invalid contents of {}"), /* REG_BADBR */
gettext_noop("Invalid character range"), /* REG_ERANGE */
gettext_noop("Out of memory"), /* REG_ESPACE */
gettext_noop("Invalid use of repetition operators"), /* REG_BADRPT */
gettext_noop("Maximum repetition in {} larger than " xstr(RE_DUP_MAX)), /* REG_BADMAX */
};
size_t
tre_regerror(int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size)
{
const char *err;
size_t err_len;
/*LINTED*/(void)&preg;
if (errcode >= 0
&& errcode < (int)(sizeof(tre_error_messages)
/ sizeof(*tre_error_messages)))
err = gettext(tre_error_messages[errcode]);
else
err = gettext("Unknown error");
err_len = strlen(err) + 1;
if (errbuf_size > 0 && errbuf != NULL)
{
if (err_len > errbuf_size)
{
strncpy(errbuf, err, errbuf_size - 1);
errbuf[errbuf_size - 1] = '\0';
}
else
{
strcpy(errbuf, err);
}
}
return err_len;
}
/* EOF */
+584
View File
@@ -0,0 +1,584 @@
/*
tre_regexec.c - TRE POSIX compatible matching functions (and more).
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */
#ifdef TRE_USE_ALLOCA
/* AIX requires this to be the first thing in the file. */
#ifndef __GNUC__
# if HAVE_ALLOCA_H
# include <alloca.h>
# else
# ifdef _AIX
#pragma alloca
# else
# ifndef alloca /* predefined by HP cc +Olibcalls */
char *alloca ();
# endif
# endif
# endif
#endif
#endif /* TRE_USE_ALLOCA */
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_WCHAR_H
#include <wchar.h>
#endif /* HAVE_WCHAR_H */
#ifdef HAVE_WCTYPE_H
#include <wctype.h>
#endif /* HAVE_WCTYPE_H */
#ifndef TRE_WCHAR
#include <ctype.h>
#endif /* !TRE_WCHAR */
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif /* HAVE_MALLOC_H */
#include <limits.h>
#include "tre-internal.h"
#include "xmalloc.h"
/* Literal alternatives are grouped by the first byte so the matcher can
* reach the relevant candidates in O(1). In nocase mode the lookup uses the
* same folded byte mapping that was applied at compile time. */
static void
tre_litopt_candidate_range(const tre_literal_opt_t *opt, unsigned char first_byte,
size_t *start, size_t *end)
{
unsigned char key = opt->nocase ? opt->fold_map[first_byte] : first_byte;
*start = opt->start_offsets[key];
*end = opt->start_offsets[key + 1];
}
static int
tre_litopt_bytes_equal(const unsigned char *haystack,
const unsigned char *needle, size_t len,
const unsigned char *fold_map)
{
size_t i;
if (fold_map == NULL)
return memcmp(haystack, needle, len) == 0;
for (i = 0; i < len; i++)
if (fold_map[haystack[i]] != needle[i])
return 0;
return 1;
}
static int
tre_litopt_contains_case(const unsigned char *haystack, size_t hay_len,
const unsigned char *needle, size_t needle_len,
int *match_end_ofs)
{
const unsigned char *p;
size_t remaining;
if (needle_len > hay_len)
return 0;
p = haystack;
remaining = hay_len;
while (remaining >= needle_len)
{
p = memchr(p, needle[0], remaining - needle_len + 1);
if (p == NULL)
return 0;
if (memcmp(p, needle, needle_len) == 0)
{
if (match_end_ofs != NULL)
*match_end_ofs = (int)(p - haystack + needle_len);
return 1;
}
remaining = hay_len - (size_t)(p - haystack) - 1;
p++;
}
return 0;
}
/* Nocase substring matching is still byte-oriented, but scanning once and
* only checking literals that share the same folded first byte avoids the
* old O(haystack * literals) restart pattern. */
static int
tre_litopt_contains_nocase(const tre_literal_opt_t *opt,
const unsigned char *haystack, size_t hay_len,
int *match_end_ofs)
{
size_t i, start, end, j;
for (i = 0; i < hay_len; i++)
{
tre_litopt_candidate_range(opt, haystack[i], &start, &end);
for (j = start; j < end; j++)
{
const tre_literal_opt_literal_t *lit = &opt->literals[j];
if (lit->len <= hay_len - i
&& tre_litopt_bytes_equal(haystack + i, lit->data, lit->len,
opt->fold_map))
{
if (match_end_ofs != NULL)
*match_end_ofs = (int)(i + lit->len);
return 1;
}
}
}
return 0;
}
static reg_errcode_t
tre_match_literal_opt(const tre_tnfa_t *tnfa, const char *string, size_t len,
int eflags, int *match_end_ofs)
{
const tre_literal_opt_t *opt = &tnfa->literal_opt;
const unsigned char *haystack = (const unsigned char *)string;
size_t start = 0, end = opt->num_literals, i;
const unsigned char *fold_map = opt->nocase ? opt->fold_map : NULL;
if ((opt->mode == TRE_LITERAL_OPT_PREFIX
|| opt->mode == TRE_LITERAL_OPT_EXACT)
&& (eflags & REG_NOTBOL))
return REG_NOMATCH;
if ((opt->mode == TRE_LITERAL_OPT_SUFFIX
|| opt->mode == TRE_LITERAL_OPT_EXACT)
&& (eflags & REG_NOTEOL))
return REG_NOMATCH;
if ((opt->mode == TRE_LITERAL_OPT_EXACT
|| opt->mode == TRE_LITERAL_OPT_PREFIX)
&& len > 0)
tre_litopt_candidate_range(opt, haystack[0], &start, &end);
if (opt->mode == TRE_LITERAL_OPT_CONTAINS)
{
if (opt->nocase)
return tre_litopt_contains_nocase(opt, haystack, len, match_end_ofs)
? REG_OK : REG_NOMATCH;
for (i = 0; i < opt->num_literals; i++)
{
const tre_literal_opt_literal_t *lit = &opt->literals[i];
if (tre_litopt_contains_case(haystack, len, lit->data, lit->len,
match_end_ofs))
return REG_OK;
}
return REG_NOMATCH;
}
for (i = start; i < end; i++)
{
const tre_literal_opt_literal_t *lit = &opt->literals[i];
switch (opt->mode)
{
case TRE_LITERAL_OPT_EXACT:
if (len == lit->len
&& tre_litopt_bytes_equal(haystack, lit->data, len, fold_map))
{
if (match_end_ofs != NULL)
*match_end_ofs = (int)len;
return REG_OK;
}
break;
case TRE_LITERAL_OPT_PREFIX:
if (len >= lit->len
&& tre_litopt_bytes_equal(haystack, lit->data, lit->len,
fold_map))
{
if (match_end_ofs != NULL)
*match_end_ofs = (int)lit->len;
return REG_OK;
}
break;
case TRE_LITERAL_OPT_SUFFIX:
if (len >= lit->len
&& tre_litopt_bytes_equal(haystack + len - lit->len, lit->data,
lit->len, fold_map))
{
if (match_end_ofs != NULL)
*match_end_ofs = (int)len;
return REG_OK;
}
break;
case TRE_LITERAL_OPT_CONTAINS:
case TRE_LITERAL_OPT_NONE:
break;
}
}
return REG_NOMATCH;
}
/* Fills the POSIX.2 regmatch_t array according to the TNFA tag and match
endpoint values. */
void
tre_fill_pmatch(size_t nmatch, regmatch_t pmatch[], int cflags,
const tre_tnfa_t *tnfa, int *tags, int match_eo)
{
tre_submatch_data_t *submatch_data;
unsigned int i, j;
int *parents;
i = 0;
if (match_eo >= 0 && !(cflags & REG_NOSUB))
{
/* Construct submatch offsets from the tags. */
DPRINT(("end tag = t%d = %d\n", tnfa->end_tag, match_eo));
submatch_data = tnfa->submatch_data;
while (i < tnfa->num_submatches && i < nmatch)
{
if (submatch_data[i].so_tag == tnfa->end_tag)
pmatch[i].rm_so = match_eo;
else
pmatch[i].rm_so = tags[submatch_data[i].so_tag];
if (submatch_data[i].eo_tag == tnfa->end_tag)
pmatch[i].rm_eo = match_eo;
else
pmatch[i].rm_eo = tags[submatch_data[i].eo_tag];
/* If either of the endpoints were not used, this submatch
was not part of the match. */
if (pmatch[i].rm_so == -1 || pmatch[i].rm_eo == -1)
pmatch[i].rm_so = pmatch[i].rm_eo = -1;
DPRINT(("pmatch[%d] = {t%d = %d, t%d = %d}\n", i,
submatch_data[i].so_tag, pmatch[i].rm_so,
submatch_data[i].eo_tag, pmatch[i].rm_eo));
i++;
}
/* Reset all submatches that are not within all of their parent
submatches. */
i = 0;
while (i < tnfa->num_submatches && i < nmatch)
{
if (pmatch[i].rm_eo == -1)
assert(pmatch[i].rm_so == -1);
assert(pmatch[i].rm_so <= pmatch[i].rm_eo);
parents = submatch_data[i].parents;
if (parents != NULL)
for (j = 0; parents[j] >= 0; j++)
{
DPRINT(("pmatch[%d] parent %d\n", i, parents[j]));
if (pmatch[i].rm_so < pmatch[parents[j]].rm_so
|| pmatch[i].rm_eo > pmatch[parents[j]].rm_eo)
pmatch[i].rm_so = pmatch[i].rm_eo = -1;
}
i++;
}
}
while (i < nmatch)
{
pmatch[i].rm_so = -1;
pmatch[i].rm_eo = -1;
i++;
}
}
/*
Wrapper functions for POSIX compatible regexp matching.
*/
int
tre_have_backrefs(const regex_t *preg)
{
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
return tnfa->have_backrefs;
}
int
tre_have_approx(const regex_t *preg)
{
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
return tnfa->have_approx;
}
static int
tre_match(const tre_tnfa_t *tnfa, const void *string, ssize_t len,
tre_str_type_t type, size_t nmatch, regmatch_t pmatch[],
int eflags)
{
reg_errcode_t status;
int *tags = NULL, eo;
if (tnfa->num_tags > 0 && nmatch > 0)
{
#ifdef TRE_USE_ALLOCA
tags = alloca(sizeof(*tags) * tnfa->num_tags);
#else /* !TRE_USE_ALLOCA */
tags = xmalloc(sizeof(*tags) * tnfa->num_tags);
#endif /* !TRE_USE_ALLOCA */
if (tags == NULL)
return REG_ESPACE;
}
if (type == STR_BYTE
&& tnfa->literal_opt.mode != TRE_LITERAL_OPT_NONE
&& (nmatch == 0 || (tnfa->cflags & REG_NOSUB))
#ifdef TRE_APPROX
&& !(eflags & REG_APPROX_MATCHER)
#endif /* TRE_APPROX */
&& !(eflags & REG_BACKTRACKING_MATCHER))
{
size_t byte_len = (len >= 0) ? (size_t)len : strlen((const char *)string);
status = tre_match_literal_opt(tnfa, string, byte_len, eflags, &eo);
/* Even when the caller asked for no submatches, regexec() still has to
* clear any pmatch entries it was handed. The normal matcher path does
* this through tre_fill_pmatch(), so mirror that behavior here. */
if (status == REG_OK && nmatch > 0)
tre_fill_pmatch(nmatch, pmatch, tnfa->cflags, tnfa, NULL, eo);
#ifndef TRE_USE_ALLOCA
if (tags)
xfree(tags);
#endif /* !TRE_USE_ALLOCA */
return status;
}
/* Dispatch to the appropriate matcher. */
if (tnfa->have_backrefs || eflags & REG_BACKTRACKING_MATCHER)
{
/* The regex has back references, use the backtracking matcher. */
if (type == STR_USER)
{
const tre_str_source *source = string;
if (source->rewind == NULL || source->compare == NULL)
{
/* The backtracking matcher requires rewind and compare
capabilities from the input stream. */
#ifndef TRE_USE_ALLOCA
if (tags)
xfree(tags);
#endif /* !TRE_USE_ALLOCA */
return REG_BADPAT;
}
}
status = tre_tnfa_run_backtrack(tnfa, string, len, type,
tags, eflags, &eo);
}
#ifdef TRE_APPROX
else if (tnfa->have_approx || eflags & REG_APPROX_MATCHER)
{
/* The regex uses approximate matching, use the approximate matcher. */
regamatch_t match;
regaparams_t params;
tre_regaparams_default(&params);
params.max_err = 0;
params.max_cost = 0;
status = tre_tnfa_run_approx(tnfa, string, len, type, tags,
&match, params, eflags, &eo);
}
#endif /* TRE_APPROX */
else
{
/* Exact matching, no back references, use the parallel matcher. */
status = tre_tnfa_run_parallel(tnfa, string, len, type,
tags, eflags, &eo);
}
if (status == REG_OK)
/* A match was found, so fill the submatch registers. */
tre_fill_pmatch(nmatch, pmatch, tnfa->cflags, tnfa, tags, eo);
#ifndef TRE_USE_ALLOCA
if (tags)
xfree(tags);
#endif /* !TRE_USE_ALLOCA */
return status;
}
int
tre_regnexec(const regex_t *preg, const char *str, size_t len,
size_t nmatch, regmatch_t pmatch[], int eflags)
{
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
tre_str_type_t type = (TRE_MB_CUR_MAX == 1) ? STR_BYTE : STR_MBS;
return tre_match(tnfa, str, len, type, nmatch, pmatch, eflags);
}
#ifdef TRE_USE_GNUC_REGEXEC_FPL
int
tre_regexec(const regex_t *preg, const char *str,
size_t nmatch, regmatch_t pmatch[_Restrict_arr_ _REGEX_NELTS (nmatch)],
int eflags)
#else
int
tre_regexec(const regex_t *preg, const char *str,
size_t nmatch, regmatch_t pmatch[], int eflags)
#endif
{
return tre_regnexec(preg, str, -1, nmatch, pmatch, eflags);
}
int
tre_regexecb(const regex_t *preg, const char *str,
size_t nmatch, regmatch_t pmatch[], int eflags)
{
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
return tre_match(tnfa, str, -1, STR_BYTE, nmatch, pmatch, eflags);
}
int
tre_regnexecb(const regex_t *preg, const char *str, size_t len,
size_t nmatch, regmatch_t pmatch[], int eflags)
{
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
return tre_match(tnfa, str, len, STR_BYTE, nmatch, pmatch, eflags);
}
#ifdef TRE_WCHAR
int
tre_regwnexec(const regex_t *preg, const wchar_t *str, size_t len,
size_t nmatch, regmatch_t pmatch[], int eflags)
{
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
return tre_match(tnfa, str, len, STR_WIDE, nmatch, pmatch, eflags);
}
int
tre_regwexec(const regex_t *preg, const wchar_t *str,
size_t nmatch, regmatch_t pmatch[], int eflags)
{
return tre_regwnexec(preg, str, -1, nmatch, pmatch, eflags);
}
#endif /* TRE_WCHAR */
int
tre_reguexec(const regex_t *preg, const tre_str_source *str,
size_t nmatch, regmatch_t pmatch[], int eflags)
{
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
return tre_match(tnfa, str, -1, STR_USER, nmatch, pmatch, eflags);
}
#ifdef TRE_APPROX
/*
Wrapper functions for approximate regexp matching.
*/
static int
tre_match_approx(const tre_tnfa_t *tnfa, const void *string, ssize_t len,
tre_str_type_t type, regamatch_t *match, regaparams_t params,
int eflags)
{
reg_errcode_t status;
int *tags = NULL, eo;
/* If the regexp does not use approximate matching features, the
maximum cost is zero, and the approximate matcher isn't forced,
use the exact matcher instead. */
if (params.max_cost == 0 && !tnfa->have_approx
&& !(eflags & REG_APPROX_MATCHER))
return tre_match(tnfa, string, len, type, match->nmatch, match->pmatch,
eflags);
/* Back references are not supported by the approximate matcher. */
if (tnfa->have_backrefs)
return REG_BADPAT;
if (tnfa->num_tags > 0 && match->nmatch > 0)
{
#if TRE_USE_ALLOCA
tags = alloca(sizeof(*tags) * tnfa->num_tags);
#else /* !TRE_USE_ALLOCA */
tags = xmalloc(sizeof(*tags) * tnfa->num_tags);
#endif /* !TRE_USE_ALLOCA */
if (tags == NULL)
return REG_ESPACE;
}
status = tre_tnfa_run_approx(tnfa, string, len, type, tags,
match, params, eflags, &eo);
if (status == REG_OK)
tre_fill_pmatch(match->nmatch, match->pmatch, tnfa->cflags, tnfa, tags, eo);
#ifndef TRE_USE_ALLOCA
if (tags)
xfree(tags);
#endif /* !TRE_USE_ALLOCA */
return status;
}
int
tre_reganexec(const regex_t *preg, const char *str, size_t len,
regamatch_t *match, regaparams_t params, int eflags)
{
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
tre_str_type_t type = (TRE_MB_CUR_MAX == 1) ? STR_BYTE : STR_MBS;
return tre_match_approx(tnfa, str, len, type, match, params, eflags);
}
int
tre_regaexec(const regex_t *preg, const char *str,
regamatch_t *match, regaparams_t params, int eflags)
{
return tre_reganexec(preg, str, -1, match, params, eflags);
}
int
tre_regaexecb(const regex_t *preg, const char *str,
regamatch_t *match, regaparams_t params, int eflags)
{
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
return tre_match_approx(tnfa, str, -1, STR_BYTE, match, params, eflags);
}
#ifdef TRE_WCHAR
int
tre_regawnexec(const regex_t *preg, const wchar_t *str, size_t len,
regamatch_t *match, regaparams_t params, int eflags)
{
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
return tre_match_approx(tnfa, str, len, STR_WIDE,
match, params, eflags);
}
int
tre_regawexec(const regex_t *preg, const wchar_t *str,
regamatch_t *match, regaparams_t params, int eflags)
{
return tre_regawnexec(preg, str, -1, match, params, eflags);
}
#endif /* TRE_WCHAR */
void
tre_regaparams_default(regaparams_t *params)
{
memset(params, 0, sizeof(*params));
params->cost_ins = 1;
params->cost_del = 1;
params->cost_subst = 1;
params->max_cost = INT_MAX;
params->max_ins = INT_MAX;
params->max_del = INT_MAX;
params->max_subst = INT_MAX;
params->max_err = INT_MAX;
}
#endif /* TRE_APPROX */
/* EOF */
+226
View File
@@ -0,0 +1,226 @@
/*
tre-ast.c - Abstract syntax tree (AST) routines
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */
#include <assert.h>
#include "tre-ast.h"
#include "tre-mem.h"
tre_ast_node_t *
tre_ast_new_node(tre_mem_t mem, tre_ast_type_t type, size_t size)
{
tre_ast_node_t *node;
node = tre_mem_calloc(mem, sizeof(*node));
if (!node)
return NULL;
node->obj = tre_mem_calloc(mem, size);
if (!node->obj)
return NULL;
node->type = type;
node->nullable = -1;
node->submatch_id = -1;
return node;
}
tre_ast_node_t *
tre_ast_new_literal(tre_mem_t mem, int code_min, int code_max)
{
tre_ast_node_t *node;
tre_literal_t *lit;
node = tre_ast_new_node(mem, LITERAL, sizeof(tre_literal_t));
if (!node)
return NULL;
lit = node->obj;
lit->code_min = code_min;
lit->code_max = code_max;
lit->position = -1;
return node;
}
tre_ast_node_t *
tre_ast_new_iter(tre_mem_t mem, tre_ast_node_t *arg, int min, int max,
int minimal)
{
tre_ast_node_t *node;
tre_iteration_t *iter;
node = tre_ast_new_node(mem, ITERATION, sizeof(tre_iteration_t));
if (!node)
return NULL;
iter = node->obj;
iter->arg = arg;
iter->min = min;
iter->max = max;
iter->minimal = minimal;
node->num_submatches = arg->num_submatches;
return node;
}
tre_ast_node_t *
tre_ast_new_union(tre_mem_t mem, tre_ast_node_t *left, tre_ast_node_t *right)
{
tre_ast_node_t *node;
node = tre_ast_new_node(mem, UNION, sizeof(tre_union_t));
if (node == NULL)
return NULL;
((tre_union_t *)node->obj)->left = left;
((tre_union_t *)node->obj)->right = right;
node->num_submatches = left->num_submatches + right->num_submatches;
return node;
}
tre_ast_node_t *
tre_ast_new_catenation(tre_mem_t mem, tre_ast_node_t *left,
tre_ast_node_t *right)
{
tre_ast_node_t *node;
node = tre_ast_new_node(mem, CATENATION, sizeof(tre_catenation_t));
if (node == NULL)
return NULL;
((tre_catenation_t *)node->obj)->left = left;
((tre_catenation_t *)node->obj)->right = right;
node->num_submatches = left->num_submatches + right->num_submatches;
return node;
}
#ifdef TRE_DEBUG
static void
tre_findent(FILE *stream, int i)
{
while (i-- > 0)
fputc(' ', stream);
}
void
tre_print_params(int *params)
{
int i;
if (params)
{
DPRINT(("params ["));
for (i = 0; i < TRE_PARAM_LAST; i++)
{
if (params[i] == TRE_PARAM_UNSET)
DPRINT(("unset"));
else if (params[i] == TRE_PARAM_DEFAULT)
DPRINT(("default"));
else
DPRINT(("%d", params[i]));
if (i < TRE_PARAM_LAST - 1)
DPRINT((", "));
}
DPRINT(("]"));
}
}
static void
tre_do_print(FILE *stream, tre_ast_node_t *ast, int indent)
{
int code_min, code_max, pos;
int num_tags = ast->num_tags;
tre_literal_t *lit;
tre_iteration_t *iter;
tre_findent(stream, indent);
switch (ast->type)
{
case LITERAL:
lit = ast->obj;
code_min = lit->code_min;
code_max = lit->code_max;
pos = lit->position;
if (IS_EMPTY(lit))
{
fprintf(stream, "literal empty\n");
}
else if (IS_ASSERTION(lit))
{
int i;
char *assertions[] = { "bol", "eol", "ctype", "!ctype",
"bow", "eow", "wb", "!wb" };
if (code_max >= ASSERT_LAST << 1)
assert(0);
fprintf(stream, "assertions: ");
for (i = 0; (1 << i) <= ASSERT_LAST; i++)
if (code_max & (1 << i))
fprintf(stream, "%s ", assertions[i]);
fprintf(stream, "\n");
}
else if (IS_TAG(lit))
{
fprintf(stream, "tag %d\n", code_max);
}
else if (IS_BACKREF(lit))
{
fprintf(stream, "backref %d, pos %d\n", code_max, pos);
}
else if (IS_PARAMETER(lit))
{
tre_print_params(lit->u.params);
fprintf(stream, "\n");
}
else
{
fprintf(stream, "literal (%c, %c) (%d, %d), pos %d, sub %d, "
"%d tags\n", code_min, code_max, code_min, code_max, pos,
ast->submatch_id, num_tags);
}
break;
case ITERATION:
iter = ast->obj;
fprintf(stream, "iteration {%d, %d}, sub %d, %d tags, %s\n",
iter->min, iter->max, ast->submatch_id, num_tags,
iter->minimal ? "minimal" : "greedy");
tre_do_print(stream, iter->arg, indent + 2);
break;
case UNION:
fprintf(stream, "union, sub %d, %d tags\n", ast->submatch_id, num_tags);
tre_do_print(stream, ((tre_union_t *)ast->obj)->left, indent + 2);
tre_do_print(stream, ((tre_union_t *)ast->obj)->right, indent + 2);
break;
case CATENATION:
fprintf(stream, "catenation, sub %d, %d tags\n", ast->submatch_id,
num_tags);
tre_do_print(stream, ((tre_catenation_t *)ast->obj)->left, indent + 2);
tre_do_print(stream, ((tre_catenation_t *)ast->obj)->right, indent + 2);
break;
default:
assert(0);
break;
}
}
static void
tre_ast_fprint(FILE *stream, tre_ast_node_t *ast)
{
tre_do_print(stream, ast, 0);
}
void
tre_ast_print(tre_ast_node_t *tree)
{
printf("AST:\n");
tre_ast_fprint(stdout, tree);
}
#endif /* TRE_DEBUG */
/* EOF */
+128
View File
@@ -0,0 +1,128 @@
/*
tre-ast.h - Abstract syntax tree (AST) definitions
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#ifndef TRE_AST_H
#define TRE_AST_H 1
#include "tre-mem.h"
#include "tre-internal.h"
#include "tre-compile.h"
/* The different AST node types. */
typedef enum {
LITERAL,
CATENATION,
ITERATION,
UNION
} tre_ast_type_t;
/* Special subtypes of TRE_LITERAL. */
#define EMPTY -1 /* Empty leaf (denotes empty string). */
#define ASSERTION -2 /* Assertion leaf. */
#define TAG -3 /* Tag leaf. */
#define BACKREF -4 /* Back reference leaf. */
#define PARAMETER -5 /* Parameter. */
#define IS_SPECIAL(x) ((x)->code_min < 0)
#define IS_EMPTY(x) ((x)->code_min == EMPTY)
#define IS_ASSERTION(x) ((x)->code_min == ASSERTION)
#define IS_TAG(x) ((x)->code_min == TAG)
#define IS_BACKREF(x) ((x)->code_min == BACKREF)
#define IS_PARAMETER(x) ((x)->code_min == PARAMETER)
/* A generic AST node. All AST nodes consist of this node on the top
level with `obj' pointing to the actual content. */
typedef struct {
tre_ast_type_t type; /* Type of the node. */
void *obj; /* Pointer to actual node. */
int nullable;
int submatch_id;
unsigned int num_submatches;
unsigned int num_tags;
tre_pos_and_tags_t *firstpos;
tre_pos_and_tags_t *lastpos;
} tre_ast_node_t;
/* A "literal" node. These are created for assertions, back references,
tags, matching parameter settings, and all expressions that match one
character. */
typedef struct {
long code_min;
long code_max;
int position;
union {
tre_ctype_t class;
int *params;
} u;
tre_ctype_t *neg_classes;
} tre_literal_t;
/* A "catenation" node. These are created when two regexps are concatenated.
If there are more than one subexpressions in sequence, the `left' part
holds all but the last, and `right' part holds the last subexpression
(catenation is left associative). */
typedef struct {
tre_ast_node_t *left;
tre_ast_node_t *right;
} tre_catenation_t;
/* An "iteration" node. These are created for the "*", "+", "?", and "{m,n}"
operators. */
typedef struct {
/* Subexpression to match. */
tre_ast_node_t *arg;
/* Minimum number of consecutive matches. */
int min;
/* Maximum number of consecutive matches. */
int max;
/* If 0, match as many characters as possible, if 1 match as few as
possible. Note that this does not always mean the same thing as
matching as many/few repetitions as possible. */
unsigned int minimal:1;
/* Approximate matching parameters (or NULL). */
int *params;
} tre_iteration_t;
/* An "union" node. These are created for the "|" operator. */
typedef struct {
tre_ast_node_t *left;
tre_ast_node_t *right;
} tre_union_t;
tre_ast_node_t *
tre_ast_new_node(tre_mem_t mem, tre_ast_type_t type, size_t size);
tre_ast_node_t *
tre_ast_new_literal(tre_mem_t mem, int code_min, int code_max);
tre_ast_node_t *
tre_ast_new_iter(tre_mem_t mem, tre_ast_node_t *arg, int min, int max,
int minimal);
tre_ast_node_t *
tre_ast_new_union(tre_mem_t mem, tre_ast_node_t *left, tre_ast_node_t *right);
tre_ast_node_t *
tre_ast_new_catenation(tre_mem_t mem, tre_ast_node_t *left,
tre_ast_node_t *right);
#ifdef TRE_DEBUG
void
tre_ast_print(tre_ast_node_t *tree);
/* XXX - rethink AST printing API */
void
tre_print_params(int *params);
#endif /* TRE_DEBUG */
#endif /* TRE_AST_H */
/* EOF */
+2673
View File
File diff suppressed because it is too large Load Diff
+27
View File
@@ -0,0 +1,27 @@
/*
tre-compile.h: Regex compilation definitions
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#ifndef TRE_COMPILE_H
#define TRE_COMPILE_H 1
typedef struct {
int position;
int code_min;
int code_max;
int *tags;
int assertions;
tre_ctype_t class;
tre_ctype_t *neg_classes;
int backref;
int *params;
} tre_pos_and_tags_t;
#endif /* TRE_COMPILE_H */
/* EOF */
+73
View File
@@ -0,0 +1,73 @@
/*
tre-filter.c: Histogram filter to quickly find regexp match candidates
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
/* The idea of this filter is quite simple. First, let's assume the
search pattern is a simple string. In order for a substring of a
longer string to match the search pattern, it must have the same
numbers of different characters as the pattern, and those
characters must occur in the same order as they occur in pattern. */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */
#include <stdio.h>
#include "tre-internal.h"
#include "tre-filter.h"
int
tre_filter_find(const unsigned char *str, size_t len, tre_filter_t *filter)
{
unsigned short counts[256];
unsigned int i;
unsigned int window_len = filter->window_len;
tre_filter_profile_t *profile = filter->profile;
const unsigned char *str_orig = str;
DPRINT(("tre_filter_find: %.*s\n", len, str));
for (i = 0; i < elementsof(counts); i++)
counts[i] = 0;
i = 0;
while (*str && i < window_len && i < len)
{
counts[*str]++;
i++;
str++;
len--;
}
while (len > 0)
{
tre_filter_profile_t *p;
counts[*str]++;
counts[*(str - window_len)]--;
p = profile;
while (p->ch)
{
if (counts[p->ch] < p->count)
break;
p++;
}
if (!p->ch)
{
DPRINT(("Found possible match at %d\n",
str - str_orig));
return str - str_orig;
}
else
{
DPRINT(("No match so far...\n"));
}
len--;
str++;
}
DPRINT(("This string cannot match.\n"));
return -1;
}
+19
View File
@@ -0,0 +1,19 @@
typedef struct {
unsigned char ch;
unsigned char count;
} tre_filter_profile_t;
typedef struct {
/* Length of the window where the character counts are kept. */
int window_len;
/* Required character counts table. */
tre_filter_profile_t *profile;
} tre_filter_t;
int
tre_filter_find(const unsigned char *str, size_t len, tre_filter_t *filter);
+319
View File
@@ -0,0 +1,319 @@
/*
tre-internal.h - TRE internal definitions
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#ifndef TRE_INTERNAL_H
#define TRE_INTERNAL_H 1
#ifdef HAVE_WCHAR_H
#include <wchar.h>
#endif /* HAVE_WCHAR_H */
#ifdef HAVE_WCTYPE_H
#include <wctype.h>
#endif /* HAVE_WCTYPE_H */
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif /* HAVE_SYS_TYPES_H */
#include <limits.h>
#include <ctype.h>
#include "../local_includes/tre.h"
#define TRE_MAX_RE 65536
#define TRE_MAX_STRING INT_MAX
#define TRE_MAX_STACK 1048576
#ifdef TRE_DEBUG
#include <stdio.h>
#define DPRINT(msg) do {printf msg; fflush(stdout);} while(/*CONSTCOND*/(void)0,0)
#else /* !TRE_DEBUG */
#define DPRINT(msg) do { } while(/*CONSTCOND*/(void)0,0)
#endif /* !TRE_DEBUG */
#define elementsof(x) ( sizeof(x) / sizeof(x[0]) )
#ifdef HAVE_MBRTOWC
#define tre_mbrtowc(pwc, s, n, ps) (mbrtowc((pwc), (s), (n), (ps)))
#else /* !HAVE_MBRTOWC */
#ifdef HAVE_MBTOWC
#define tre_mbrtowc(pwc, s, n, ps) (mbtowc((pwc), (s), (n)))
#endif /* HAVE_MBTOWC */
#endif /* !HAVE_MBRTOWC */
#ifdef TRE_MULTIBYTE
#ifdef HAVE_MBSTATE_T
#define TRE_MBSTATE
#endif /* TRE_MULTIBYTE */
#endif /* HAVE_MBSTATE_T */
/* Define the character types and functions. */
#ifdef TRE_WCHAR
/* Wide characters. */
typedef wint_t tre_cint_t;
#if WCHAR_MAX <= INT_MAX
#define TRE_CHAR_MAX WCHAR_MAX
#else /* WCHAR_MAX > INT_MAX */
#define TRE_CHAR_MAX INT_MAX
#endif
#ifdef TRE_MULTIBYTE
#define TRE_MB_CUR_MAX MB_CUR_MAX
#else /* !TRE_MULTIBYTE */
#define TRE_MB_CUR_MAX 1
#endif /* !TRE_MULTIBYTE */
#define tre_isalnum iswalnum
#define tre_isalpha iswalpha
#ifdef HAVE_ISWBLANK
#define tre_isblank iswblank
#endif /* HAVE_ISWBLANK */
#define tre_iscntrl iswcntrl
#define tre_isdigit iswdigit
#define tre_isgraph iswgraph
#define tre_islower iswlower
#define tre_isprint iswprint
#define tre_ispunct iswpunct
#define tre_isspace iswspace
#define tre_isupper iswupper
#define tre_isxdigit iswxdigit
#define tre_tolower towlower
#define tre_toupper towupper
#define tre_strlen wcslen
#else /* !TRE_WCHAR */
/* 8 bit characters. */
typedef short tre_cint_t;
#define TRE_CHAR_MAX 255
#define TRE_MB_CUR_MAX 1
#define tre_isalnum isalnum
#define tre_isalpha isalpha
#ifdef HAVE_ISASCII
#define tre_isascii isascii
#endif /* HAVE_ISASCII */
#ifdef HAVE_ISBLANK
#define tre_isblank isblank
#endif /* HAVE_ISBLANK */
#define tre_iscntrl iscntrl
#define tre_isdigit isdigit
#define tre_isgraph isgraph
#define tre_islower islower
#define tre_isprint isprint
#define tre_ispunct ispunct
#define tre_isspace isspace
#define tre_isupper isupper
#define tre_isxdigit isxdigit
#define tre_tolower(c) (tre_cint_t)(tolower(c))
#define tre_toupper(c) (tre_cint_t)(toupper(c))
#define tre_strlen(s) (strlen((const char*)s))
#endif /* !TRE_WCHAR */
#if defined(TRE_WCHAR) && defined(HAVE_ISWCTYPE) && defined(HAVE_WCTYPE)
#define TRE_USE_SYSTEM_WCTYPE 1
#endif
#ifdef TRE_USE_SYSTEM_WCTYPE
/* Use system provided iswctype() and wctype(). */
typedef wctype_t tre_ctype_t;
#define tre_isctype iswctype
#define tre_ctype wctype
#else /* !TRE_USE_SYSTEM_WCTYPE */
/* Define our own versions of iswctype() and wctype(). */
typedef int (*tre_ctype_t)(tre_cint_t);
#define tre_isctype(c, type) ( (type)(c) )
tre_ctype_t tre_ctype(const char *name);
#endif /* !TRE_USE_SYSTEM_WCTYPE */
typedef enum { STR_WIDE, STR_BYTE, STR_MBS, STR_USER } tre_str_type_t;
/* Returns number of bytes to add to (char *)ptr to make it
properly aligned for the type. */
#define ALIGN(ptr, type) \
((((long)ptr) % sizeof(type)) \
? (sizeof(type) - (((long)ptr) % sizeof(type))) \
: 0)
#undef MAX
#undef MIN
#define MAX(a, b) (((a) >= (b)) ? (a) : (b))
#define MIN(a, b) (((a) <= (b)) ? (a) : (b))
/* Define STRF to the correct printf formatter for strings. */
#ifdef TRE_WCHAR
#define STRF "ls"
#else /* !TRE_WCHAR */
#define STRF "s"
#endif /* !TRE_WCHAR */
/* TNFA transition type. A TNFA state is an array of transitions,
the terminator is a transition with NULL `state'. */
typedef struct tnfa_transition tre_tnfa_transition_t;
struct tnfa_transition {
/* Range of accepted characters. */
tre_cint_t code_min;
tre_cint_t code_max;
/* Pointer to the destination state. */
tre_tnfa_transition_t *state;
/* ID number of the destination state. */
int state_id;
/* -1 terminated array of tags (or NULL). */
int *tags;
/* Matching parameters settings (or NULL). */
int *params;
/* Assertion bitmap. */
int assertions;
/* Assertion parameters. */
union {
/* Character class assertion. */
tre_ctype_t class;
/* Back reference assertion. */
int backref;
} u;
/* Negative character class assertions. */
tre_ctype_t *neg_classes;
};
/* Assertions. */
#define ASSERT_AT_BOL 1 /* Beginning of line. */
#define ASSERT_AT_EOL 2 /* End of line. */
#define ASSERT_CHAR_CLASS 4 /* Character class in `class'. */
#define ASSERT_CHAR_CLASS_NEG 8 /* Character classes in `neg_classes'. */
#define ASSERT_AT_BOW 16 /* Beginning of word. */
#define ASSERT_AT_EOW 32 /* End of word. */
#define ASSERT_AT_WB 64 /* Word boundary. */
#define ASSERT_AT_WB_NEG 128 /* Not a word boundary. */
#define ASSERT_BACKREF 256 /* A back reference in `backref'. */
#define ASSERT_LAST 256
/* Tag directions. */
typedef enum {
TRE_TAG_MINIMIZE = 0,
TRE_TAG_MAXIMIZE = 1
} tre_tag_direction_t;
/* Parameters that can be changed dynamically while matching. */
typedef enum {
TRE_PARAM_COST_INS = 0,
TRE_PARAM_COST_DEL = 1,
TRE_PARAM_COST_SUBST = 2,
TRE_PARAM_COST_MAX = 3,
TRE_PARAM_MAX_INS = 4,
TRE_PARAM_MAX_DEL = 5,
TRE_PARAM_MAX_SUBST = 6,
TRE_PARAM_MAX_ERR = 7,
TRE_PARAM_DEPTH = 8,
TRE_PARAM_LAST = 9
} tre_param_t;
/* Unset matching parameter */
#define TRE_PARAM_UNSET -1
/* Signifies the default matching parameter value. */
#define TRE_PARAM_DEFAULT -2
/* Instructions to compute submatch register values from tag values
after a successful match. */
struct tre_submatch_data {
/* Tag that gives the value for rm_so (submatch start offset). */
int so_tag;
/* Tag that gives the value for rm_eo (submatch end offset). */
int eo_tag;
/* List of submatches this submatch is contained in. */
int *parents;
};
typedef struct tre_submatch_data tre_submatch_data_t;
typedef enum {
TRE_LITERAL_OPT_NONE = 0,
TRE_LITERAL_OPT_CONTAINS,
TRE_LITERAL_OPT_PREFIX,
TRE_LITERAL_OPT_SUFFIX,
TRE_LITERAL_OPT_EXACT
} tre_literal_opt_mode_t;
typedef struct {
unsigned char *data;
size_t len;
} tre_literal_opt_literal_t;
typedef struct {
tre_literal_opt_mode_t mode;
int nocase;
size_t num_literals;
/* Folded byte mapping used by the nocase fast path. */
unsigned char fold_map[256];
/* Literal index ranges grouped by the first literal byte. */
size_t start_offsets[257];
tre_literal_opt_literal_t *literals;
} tre_literal_opt_t;
/* TNFA definition. */
typedef struct tnfa tre_tnfa_t;
struct tnfa {
tre_tnfa_transition_t *transitions;
unsigned int num_transitions;
tre_tnfa_transition_t *initial;
tre_tnfa_transition_t *final;
tre_submatch_data_t *submatch_data;
char *firstpos_chars;
int first_char;
unsigned int num_submatches;
tre_tag_direction_t *tag_directions;
int *minimal_tags;
int num_tags;
int num_minimals;
int end_tag;
int num_states;
int cflags;
int have_backrefs;
int have_approx;
int params_depth;
tre_literal_opt_t literal_opt;
};
int
tre_compile(regex_t *preg, const tre_char_t *regex, size_t n, int cflags);
void
tre_free(regex_t *preg);
void
tre_fill_pmatch(size_t nmatch, regmatch_t pmatch[], int cflags,
const tre_tnfa_t *tnfa, int *tags, int match_eo);
reg_errcode_t
tre_tnfa_run_parallel(const tre_tnfa_t *tnfa, const void *string, ssize_t len,
tre_str_type_t type, int *match_tags, int eflags,
int *match_end_ofs);
reg_errcode_t
tre_tnfa_run_backtrack(const tre_tnfa_t *tnfa, const void *string, ssize_t len,
tre_str_type_t type, int *match_tags, int eflags,
int *match_end_ofs);
#ifdef TRE_APPROX
reg_errcode_t
tre_tnfa_run_approx(const tre_tnfa_t *tnfa, const void *string, ssize_t len,
tre_str_type_t type, int *match_tags, regamatch_t *match,
regaparams_t params, int eflags, int *match_end_ofs);
#endif /* TRE_APPROX */
#endif /* TRE_INTERNAL_H */
/* EOF */
+676
View File
@@ -0,0 +1,676 @@
/*
tre-match-backtrack.c - TRE backtracking regex matching engine
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
/*
This matcher is for regexps that use back referencing. Regexp matching
with back referencing is an NP-complete problem on the number of back
references. The easiest way to match them is to use a backtracking
routine which basically goes through all possible paths in the TNFA
and chooses the one which results in the best (leftmost and longest)
match. This can be spectacularly expensive and may run out of stack
space, but there really is no better known generic algorithm. Quoting
Henry Spencer from comp.compilers:
<URL: http://compilers.iecc.com/comparch/article/93-03-102>
POSIX.2 REs require longest match, which is really exciting to
implement since the obsolete ("basic") variant also includes
\<digit>. I haven't found a better way of tackling this than doing
a preliminary match using a DFA (or simulation) on a modified RE
that just replicates subREs for \<digit>, and then doing a
backtracking match to determine whether the subRE matches were
right. This can be rather slow, but I console myself with the
thought that people who use \<digit> deserve very slow execution.
(Pun unintentional but very appropriate.)
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */
#ifdef TRE_USE_ALLOCA
/* AIX requires this to be the first thing in the file. */
#ifndef __GNUC__
# if HAVE_ALLOCA_H
# include <alloca.h>
# else
# ifdef _AIX
#pragma alloca
# else
# ifndef alloca /* predefined by HP cc +Olibcalls */
char *alloca ();
# endif
# endif
# endif
#endif
#endif /* TRE_USE_ALLOCA */
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_WCHAR_H
#include <wchar.h>
#endif /* HAVE_WCHAR_H */
#ifdef HAVE_WCTYPE_H
#include <wctype.h>
#endif /* HAVE_WCTYPE_H */
#ifndef TRE_WCHAR
#include <ctype.h>
#endif /* !TRE_WCHAR */
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif /* HAVE_MALLOC_H */
#include "tre-internal.h"
#include "tre-mem.h"
#include "tre-match-utils.h"
#include "xmalloc.h"
typedef struct {
int pos;
const char *str_byte;
#ifdef TRE_WCHAR
const wchar_t *str_wide;
#endif /* TRE_WCHAR */
tre_tnfa_transition_t *state;
int state_id;
int next_c;
int *tags;
#ifdef TRE_MBSTATE
mbstate_t mbstate;
#endif /* TRE_MBSTATE */
} tre_backtrack_item_t;
typedef struct tre_backtrack_struct {
tre_backtrack_item_t item;
struct tre_backtrack_struct *prev;
struct tre_backtrack_struct *next;
} *tre_backtrack_t;
#ifdef TRE_WCHAR
#define BT_STACK_WIDE_IN(_str_wide) stack->item.str_wide = (_str_wide)
#define BT_STACK_WIDE_OUT (str_wide) = stack->item.str_wide
#else /* !TRE_WCHAR */
#define BT_STACK_WIDE_IN(_str_wide)
#define BT_STACK_WIDE_OUT
#endif /* !TRE_WCHAR */
#ifdef TRE_MBSTATE
#define BT_STACK_MBSTATE_IN stack->item.mbstate = (mbstate)
#define BT_STACK_MBSTATE_OUT (mbstate) = stack->item.mbstate
#else /* !TRE_MBSTATE */
#define BT_STACK_MBSTATE_IN
#define BT_STACK_MBSTATE_OUT
#endif /* !TRE_MBSTATE */
#ifdef TRE_USE_ALLOCA
#define tre_bt_mem_new tre_mem_newa
#define tre_bt_mem_alloc tre_mem_alloca
#define tre_bt_mem_destroy(obj) do { } while (0)
#define xafree(obj) do { } while (0) /* do nothing, obj was obtained with alloca() */
#else /* !TRE_USE_ALLOCA */
#define tre_bt_mem_new tre_mem_new
#define tre_bt_mem_alloc tre_mem_alloc
#define tre_bt_mem_destroy tre_mem_destroy
#define xafree(obj) xfree(obj)
#endif /* !TRE_USE_ALLOCA */
#define BT_STACK_PUSH(_pos, _str_byte, _str_wide, _state, _state_id, _next_c, _tags, _mbstate) \
do \
{ \
int i; \
if (!stack->next) \
{ \
tre_backtrack_t s; \
s = tre_bt_mem_alloc(mem, sizeof(*s)); \
if (!s) \
{ \
tre_bt_mem_destroy(mem); \
if (tags) \
xafree(tags); \
if (pmatch) \
xafree(pmatch); \
if (states_seen) \
xafree(states_seen); \
return REG_ESPACE; \
} \
s->prev = stack; \
s->next = NULL; \
s->item.tags = tre_bt_mem_alloc(mem, \
sizeof(*tags) * tnfa->num_tags); \
if (!s->item.tags) \
{ \
tre_bt_mem_destroy(mem); \
if (tags) \
xafree(tags); \
if (pmatch) \
xafree(pmatch); \
if (states_seen) \
xafree(states_seen); \
return REG_ESPACE; \
} \
stack->next = s; \
stack = s; \
} \
else \
stack = stack->next; \
stack->item.pos = (_pos); \
stack->item.str_byte = (_str_byte); \
BT_STACK_WIDE_IN(_str_wide); \
stack->item.state = (_state); \
stack->item.state_id = (_state_id); \
stack->item.next_c = (_next_c); \
for (i = 0; i < tnfa->num_tags; i++) \
stack->item.tags[i] = (_tags)[i]; \
BT_STACK_MBSTATE_IN; \
} \
while (/*CONSTCOND*/(void)0,0)
#define BT_STACK_POP() \
do \
{ \
int i; \
assert(stack->prev); \
pos = stack->item.pos; \
if (type == STR_USER) \
str_source->rewind(pos + pos_add_next, str_source->context); \
str_byte = stack->item.str_byte; \
BT_STACK_WIDE_OUT; \
state = stack->item.state; \
next_c = (tre_char_t) stack->item.next_c; \
for (i = 0; i < tnfa->num_tags; i++) \
tags[i] = stack->item.tags[i]; \
BT_STACK_MBSTATE_OUT; \
stack = stack->prev; \
} \
while (/*CONSTCOND*/(void)0,0)
#undef MIN
#define MIN(a, b) ((a) <= (b) ? (a) : (b))
reg_errcode_t
tre_tnfa_run_backtrack(const tre_tnfa_t *tnfa, const void *string,
ssize_t len, tre_str_type_t type, int *match_tags,
int eflags, int *match_end_ofs)
{
/* State variables required by GET_NEXT_WCHAR. */
tre_char_t prev_c = 0, next_c = 0;
const char *str_byte = string;
ssize_t pos = 0;
unsigned int pos_add_next = 1;
#ifdef TRE_WCHAR
const wchar_t *str_wide = string;
#ifdef TRE_MBSTATE
mbstate_t mbstate;
#endif /* TRE_MBSTATE */
#endif /* TRE_WCHAR */
int reg_notbol = eflags & REG_NOTBOL;
int reg_noteol = eflags & REG_NOTEOL;
int reg_newline = tnfa->cflags & REG_NEWLINE;
int str_user_end = 0;
/* These are used to remember the necessary values of the above
variables to return to the position where the current search
started from. */
int next_c_start;
const char *str_byte_start;
int pos_start = -1;
#ifdef TRE_WCHAR
const wchar_t *str_wide_start;
#endif /* TRE_WCHAR */
#ifdef TRE_MBSTATE
mbstate_t mbstate_start;
#endif /* TRE_MBSTATE */
reg_errcode_t ret;
/* End offset of best match so far, or -1 if no match found yet. */
int match_eo = -1;
/* Tag arrays. */
int *next_tags, *tags = NULL;
/* Current TNFA state. */
tre_tnfa_transition_t *state;
int *states_seen = NULL;
/* Memory allocator to for allocating the backtracking stack. */
tre_mem_t mem = tre_bt_mem_new();
/* The backtracking stack. */
tre_backtrack_t stack;
tre_tnfa_transition_t *trans_i;
regmatch_t *pmatch = NULL;
/*
* TRE internals tend to use int instead of size_t for positions or
* lengths and don't check for overflow. This will take time to fix
* properly. In the meantime, simply limit the input to what we can
* handle.
*/
if (len > TRE_MAX_STRING)
len = TRE_MAX_STRING;
#ifdef TRE_MBSTATE
memset(&mbstate, '\0', sizeof(mbstate));
#endif /* TRE_MBSTATE */
if (!mem)
return REG_ESPACE;
stack = tre_bt_mem_alloc(mem, sizeof(*stack));
if (!stack)
{
ret = REG_ESPACE;
goto error_exit;
}
stack->prev = NULL;
stack->next = NULL;
DPRINT(("tnfa_execute_backtrack, input type %d\n", type));
DPRINT(("len = %zd\n", len));
#ifdef TRE_USE_ALLOCA
tags = alloca(sizeof(*tags) * tnfa->num_tags);
pmatch = alloca(sizeof(*pmatch) * tnfa->num_submatches);
states_seen = alloca(sizeof(*states_seen) * tnfa->num_states);
#else /* !TRE_USE_ALLOCA */
if (tnfa->num_tags)
{
tags = xmalloc(sizeof(*tags) * tnfa->num_tags);
if (!tags)
{
ret = REG_ESPACE;
goto error_exit;
}
}
if (tnfa->num_submatches)
{
pmatch = xmalloc(sizeof(*pmatch) * tnfa->num_submatches);
if (!pmatch)
{
ret = REG_ESPACE;
goto error_exit;
}
}
if (tnfa->num_states)
{
states_seen = xmalloc(sizeof(*states_seen) * tnfa->num_states);
if (!states_seen)
{
ret = REG_ESPACE;
goto error_exit;
}
}
#endif /* !TRE_USE_ALLOCA */
retry:
{
int i;
for (i = 0; i < tnfa->num_tags; i++)
{
tags[i] = -1;
if (match_tags)
match_tags[i] = -1;
}
for (i = 0; i < tnfa->num_states; i++)
states_seen[i] = 0;
}
state = NULL;
pos = pos_start;
if (type == STR_USER)
str_source->rewind(pos + pos_add_next, str_source->context);
GET_NEXT_WCHAR();
pos_start = pos;
next_c_start = next_c;
str_byte_start = str_byte;
#ifdef TRE_WCHAR
str_wide_start = str_wide;
#endif /* TRE_WCHAR */
#ifdef TRE_MBSTATE
mbstate_start = mbstate;
#endif /* TRE_MBSTATE */
/* Handle initial states. */
next_tags = NULL;
for (trans_i = tnfa->initial; trans_i->state; trans_i++)
{
DPRINT(("> init %p, prev_c %lc\n", trans_i->state, (tre_cint_t)prev_c));
if (trans_i->assertions && CHECK_ASSERTIONS(trans_i->assertions))
{
DPRINT(("assert failed\n"));
continue;
}
if (state == NULL)
{
/* Start from this state. */
state = trans_i->state;
next_tags = trans_i->tags;
}
else
{
/* Backtrack to this state. */
DPRINT(("saving state %d for backtracking\n", trans_i->state_id));
BT_STACK_PUSH(pos, str_byte, str_wide, trans_i->state,
trans_i->state_id, next_c, tags, mbstate);
{
int *tmp = trans_i->tags;
if (tmp)
while (*tmp >= 0)
stack->item.tags[*tmp++] = pos;
}
}
}
if (next_tags)
for (; *next_tags >= 0; next_tags++)
tags[*next_tags] = pos;
DPRINT(("entering match loop, pos %zd, str_byte %p\n", pos, str_byte));
DPRINT(("pos:chr/code | state and tags\n"));
DPRINT(("-------------+------------------------------------------------\n"));
if (state == NULL)
goto backtrack;
while (/*CONSTCOND*/(void)1,1)
{
tre_tnfa_transition_t *next_state;
int empty_br_match;
DPRINT(("start loop\n"));
if (state == tnfa->final)
{
DPRINT((" match found, %d %zd\n", match_eo, pos));
if (match_eo < pos
|| (match_eo == pos
&& match_tags
&& tre_tag_order(tnfa->num_tags, tnfa->tag_directions,
tags, match_tags)))
{
int i;
/* This match wins the previous match. */
DPRINT((" win previous\n"));
match_eo = pos;
if (match_tags)
for (i = 0; i < tnfa->num_tags; i++)
match_tags[i] = tags[i];
}
/* Our TNFAs never have transitions leaving from the final state,
so we jump right to backtracking. */
goto backtrack;
}
#ifdef TRE_DEBUG
DPRINT(("%3zd:%2lc/%05d | %p ", pos, (tre_cint_t)next_c, (int)next_c,
state));
{
int i;
for (i = 0; i < tnfa->num_tags; i++)
DPRINT(("%d%s", tags[i], i < tnfa->num_tags - 1 ? ", " : ""));
DPRINT(("\n"));
}
#endif /* TRE_DEBUG */
/* Go to the next character in the input string. */
empty_br_match = 0;
trans_i = state;
if (trans_i->state && trans_i->assertions & ASSERT_BACKREF)
{
/* This is a back reference state. All transitions leaving from
this state have the same back reference "assertion". Instead
of reading the next character, we match the back reference. */
int so, eo, bt = trans_i->u.backref;
int bt_len;
int result;
DPRINT((" should match back reference %d\n", bt));
/* Get the substring we need to match against. Remember to
turn off REG_NOSUB temporarily. */
tre_fill_pmatch(bt + 1, pmatch, tnfa->cflags & ~REG_NOSUB,
tnfa, tags, pos);
so = pmatch[bt].rm_so;
eo = pmatch[bt].rm_eo;
bt_len = eo - so;
#ifdef TRE_DEBUG
{
int slen;
if (len < 0)
slen = bt_len;
else
slen = MIN(bt_len, len - pos);
if (type == STR_BYTE)
{
DPRINT((" substring (len %d) is [%d, %d[: '%.*s'\n",
bt_len, so, eo, bt_len, (char*)string + so));
DPRINT((" current string is '%.*s'\n", slen, str_byte - 1));
}
#ifdef TRE_WCHAR
else if (type == STR_WIDE)
{
DPRINT((" substring (len %d) is [%d, %d[: '%.*" STRF "'\n",
bt_len, so, eo, bt_len, (wchar_t*)string + so));
DPRINT((" current string is '%.*" STRF "'\n",
slen, str_wide - 1));
}
#endif /* TRE_WCHAR */
}
#endif
if (len < 0)
{
if (type == STR_USER)
result = str_source->compare((unsigned)so, (unsigned)pos,
(unsigned)bt_len,
str_source->context);
#ifdef TRE_WCHAR
else if (type == STR_WIDE)
result = wcsncmp((const wchar_t*)string + so, str_wide - 1,
(size_t)bt_len);
#endif /* TRE_WCHAR */
else
result = strncmp((const char*)string + so, str_byte - 1,
(size_t)bt_len);
}
else if (len - pos < bt_len)
result = 1;
#ifdef TRE_WCHAR
else if (type == STR_WIDE)
result = wmemcmp((const wchar_t*)string + so, str_wide - 1,
(size_t)bt_len);
#endif /* TRE_WCHAR */
else
result = memcmp((const char*)string + so, str_byte - 1,
(size_t)bt_len);
if (result == 0)
{
/* Back reference matched. Check for infinite loop. */
if (bt_len == 0)
empty_br_match = 1;
if (empty_br_match && states_seen[trans_i->state_id])
{
DPRINT((" avoid loop\n"));
goto backtrack;
}
states_seen[trans_i->state_id] = empty_br_match;
/* Advance in input string and resync `prev_c', `next_c'
and pos. */
DPRINT((" back reference matched\n"));
str_byte += bt_len - 1;
#ifdef TRE_WCHAR
str_wide += bt_len - 1;
#endif /* TRE_WCHAR */
pos += bt_len - 1;
GET_NEXT_WCHAR();
DPRINT((" pos now %zd\n", pos));
}
else
{
DPRINT((" back reference did not match\n"));
goto backtrack;
}
}
else
{
/* Check for end of string. */
if (len < 0)
{
if (type == STR_USER)
{
if (str_user_end)
goto backtrack;
}
else if (next_c == L'\0' || pos >= TRE_MAX_STRING)
goto backtrack;
}
else
{
if (pos >= len)
goto backtrack;
}
/* Read the next character. */
GET_NEXT_WCHAR();
}
next_state = NULL;
for (trans_i = state; trans_i->state; trans_i++)
{
DPRINT((" transition %d-%d (%c-%c) %d to %d\n",
trans_i->code_min, trans_i->code_max,
trans_i->code_min, trans_i->code_max,
trans_i->assertions, trans_i->state_id));
if (trans_i->code_min <= (tre_cint_t)prev_c
&& trans_i->code_max >= (tre_cint_t)prev_c)
{
if (trans_i->assertions
&& (CHECK_ASSERTIONS(trans_i->assertions)
|| CHECK_CHAR_CLASSES(trans_i, tnfa, eflags)))
{
DPRINT((" assertion failed\n"));
continue;
}
if (next_state == NULL)
{
/* First matching transition. */
DPRINT((" Next state is %d\n", trans_i->state_id));
next_state = trans_i->state;
next_tags = trans_i->tags;
}
else
{
/* Second matching transition. We may need to backtrack here
to take this transition instead of the first one, so we
push this transition in the backtracking stack so we can
jump back here if needed. */
DPRINT((" saving state %d for backtracking\n",
trans_i->state_id));
BT_STACK_PUSH(pos, str_byte, str_wide, trans_i->state,
trans_i->state_id, next_c, tags, mbstate);
{
int *tmp;
for (tmp = trans_i->tags; tmp && *tmp >= 0; tmp++)
stack->item.tags[*tmp] = pos;
}
#if 0 /* XXX - it's important not to look at all transitions here to keep
the stack small! */
break;
#endif
}
}
}
if (next_state != NULL)
{
/* Matching transitions were found. Take the first one. */
state = next_state;
/* Update the tag values. */
if (next_tags)
while (*next_tags >= 0)
tags[*next_tags++] = pos;
}
else
{
backtrack:
/* A matching transition was not found. Try to backtrack. */
if (stack->prev)
{
DPRINT((" backtracking\n"));
if (stack->item.state->assertions & ASSERT_BACKREF)
{
DPRINT((" states_seen[%d] = 0\n",
stack->item.state_id));
states_seen[stack->item.state_id] = 0;
}
BT_STACK_POP();
}
else if (match_eo < 0)
{
/* Try starting from a later position in the input string. */
/* Check for end of string. */
if (len < 0)
{
if (next_c_start == L'\0' || pos_start >= TRE_MAX_STRING)
{
DPRINT(("end of string.\n"));
break;
}
}
else
{
if (pos_start >= len)
{
DPRINT(("end of string.\n"));
break;
}
}
DPRINT(("restarting from next start position\n"));
next_c = (tre_char_t) next_c_start;
#ifdef TRE_MBSTATE
mbstate = mbstate_start;
#endif /* TRE_MBSTATE */
str_byte = str_byte_start;
#ifdef TRE_WCHAR
str_wide = str_wide_start;
#endif /* TRE_WCHAR */
goto retry;
}
else
{
DPRINT(("finished\n"));
break;
}
}
}
ret = match_eo >= 0 ? REG_OK : REG_NOMATCH;
*match_end_ofs = match_eo;
error_exit:
tre_bt_mem_destroy(mem);
#ifndef TRE_USE_ALLOCA
if (tags)
xafree(tags);
if (pmatch)
xafree(pmatch);
if (states_seen)
xafree(states_seen);
#endif /* !TRE_USE_ALLOCA */
return ret;
}
+538
View File
@@ -0,0 +1,538 @@
/*
tre-match-parallel.c - TRE parallel regex matching engine
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
/*
This algorithm searches for matches basically by reading characters
in the searched string one by one, starting at the beginning. All
matching paths in the TNFA are traversed in parallel. When two or
more paths reach the same state, exactly one is chosen according to
tag ordering rules; if returning submatches is not required it does
not matter which path is chosen.
The worst case time required for finding the leftmost and longest
match, or determining that there is no match, is always linearly
dependent on the length of the text being searched.
This algorithm cannot handle TNFAs with back referencing nodes.
See `tre-match-backtrack.c'.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */
#ifdef TRE_USE_ALLOCA
/* AIX requires this to be the first thing in the file. */
#ifndef __GNUC__
# if HAVE_ALLOCA_H
# include <alloca.h>
# else
# ifdef _AIX
#pragma alloca
# else
# ifndef alloca /* predefined by HP cc +Olibcalls */
char *alloca ();
# endif
# endif
# endif
#endif
#endif /* TRE_USE_ALLOCA */
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_WCHAR_H
#include <wchar.h>
#endif /* HAVE_WCHAR_H */
#ifdef HAVE_WCTYPE_H
#include <wctype.h>
#endif /* HAVE_WCTYPE_H */
#ifndef TRE_WCHAR
#include <ctype.h>
#endif /* !TRE_WCHAR */
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif /* HAVE_MALLOC_H */
#include "tre-internal.h"
#include "tre-match-utils.h"
#include "xmalloc.h"
typedef struct {
tre_tnfa_transition_t *state;
int *tags;
} tre_tnfa_reach_t;
typedef struct {
int pos;
int **tags;
} tre_reach_pos_t;
#ifdef TRE_DEBUG
static void
tre_print_reach(const tre_tnfa_reach_t *reach, int num_tags)
{
int i;
while (reach->state != NULL)
{
DPRINT((" %p", (void *)reach->state));
if (num_tags > 0)
{
DPRINT(("/"));
for (i = 0; i < num_tags; i++)
{
DPRINT(("%d:%d", i, reach->tags[i]));
if (i < (num_tags-1))
DPRINT((","));
}
}
reach++;
}
DPRINT(("\n"));
}
#endif /* TRE_DEBUG */
reg_errcode_t
tre_tnfa_run_parallel(const tre_tnfa_t *tnfa, const void *string, ssize_t len,
tre_str_type_t type, int *match_tags, int eflags,
int *match_end_ofs)
{
/* State variables required by GET_NEXT_WCHAR. */
tre_char_t prev_c = 0, next_c = 0;
const char *str_byte = string;
ssize_t pos = -1;
unsigned int pos_add_next = 1;
#ifdef TRE_WCHAR
const wchar_t *str_wide = string;
#ifdef TRE_MBSTATE
mbstate_t mbstate;
#endif /* TRE_MBSTATE */
#endif /* TRE_WCHAR */
reg_errcode_t ret;
int reg_notbol = eflags & REG_NOTBOL;
int reg_noteol = eflags & REG_NOTEOL;
int reg_newline = tnfa->cflags & REG_NEWLINE;
int str_user_end = 0;
char *buf;
tre_tnfa_transition_t *trans_i;
tre_tnfa_reach_t *reach, *reach_next, *reach_i, *reach_next_i;
tre_reach_pos_t *reach_pos;
int *tag_i;
int num_tags, i;
int match_eo = -1; /* end offset of match (-1 if no match found yet) */
int new_match = 0;
int *tmp_tags = NULL;
int *tmp_iptr;
/*
* TRE internals tend to use int instead of size_t for positions or
* lengths and don't check for overflow. This will take time to fix
* properly. In the meantime, simply limit the input to what we can
* handle.
*/
if (len > TRE_MAX_STRING)
len = TRE_MAX_STRING;
#ifdef TRE_MBSTATE
memset(&mbstate, '\0', sizeof(mbstate));
#endif /* TRE_MBSTATE */
DPRINT(("tre_tnfa_run_parallel, input type %d\n", type));
if (!match_tags)
num_tags = 0;
else
num_tags = tnfa->num_tags;
/* Allocate memory for temporary data required for matching. This needs to
be done for every matching operation to be thread safe. This allocates
everything in a single large block from the stack frame using alloca()
or with malloc() if alloca is unavailable. */
{
size_t tbytes, rbytes, pbytes, xbytes, total_bytes;
size_t num_states = (size_t)tnfa->num_states;
size_t state_tag_bytes, reach_bytes;
size_t padding = (sizeof(long) - 1) * 4;
char *tmp_buf;
if (num_states > SIZE_MAX / sizeof(*reach_pos))
return REG_ESPACE;
pbytes = sizeof(*reach_pos) * num_states;
if (num_states + 1 > SIZE_MAX / sizeof(*reach_next))
return REG_ESPACE;
rbytes = sizeof(*reach_next) * (num_states + 1);
if ((size_t)num_tags > SIZE_MAX / sizeof(*tmp_tags))
return REG_ESPACE;
tbytes = sizeof(*tmp_tags) * (size_t)num_tags;
if ((size_t)num_tags > SIZE_MAX / sizeof(int))
return REG_ESPACE;
xbytes = sizeof(int) * (size_t)num_tags;
if (num_states > 0 && xbytes > SIZE_MAX / num_states)
return REG_ESPACE;
state_tag_bytes = xbytes * num_states;
if (rbytes > SIZE_MAX - state_tag_bytes)
return REG_ESPACE;
reach_bytes = rbytes + state_tag_bytes;
if (reach_bytes > (SIZE_MAX - padding - tbytes - pbytes) / 2)
return REG_ESPACE;
/* Compute the length of the block we need. */
total_bytes =
padding + reach_bytes * 2 + tbytes + pbytes;
/* Allocate the memory. */
#ifdef TRE_USE_ALLOCA
buf = alloca(total_bytes);
#else /* !TRE_USE_ALLOCA */
buf = xmalloc(total_bytes);
#endif /* !TRE_USE_ALLOCA */
if (buf == NULL)
return REG_ESPACE;
memset(buf, 0, total_bytes);
/* Get the various pointers within tmp_buf (properly aligned). */
tmp_tags = (void *)buf;
tmp_buf = buf + tbytes;
tmp_buf += ALIGN(tmp_buf, long);
reach_next = (void *)tmp_buf;
tmp_buf += rbytes;
tmp_buf += ALIGN(tmp_buf, long);
reach = (void *)tmp_buf;
tmp_buf += rbytes;
tmp_buf += ALIGN(tmp_buf, long);
reach_pos = (void *)tmp_buf;
tmp_buf += pbytes;
tmp_buf += ALIGN(tmp_buf, long);
for (i = 0; i < tnfa->num_states; i++)
{
reach[i].tags = (void *)tmp_buf;
tmp_buf += xbytes;
reach_next[i].tags = (void *)tmp_buf;
tmp_buf += xbytes;
}
}
for (i = 0; i < tnfa->num_states; i++)
reach_pos[i].pos = -1;
/* If only one character can start a match, find it first. */
if (tnfa->first_char >= 0 && type == STR_BYTE && str_byte)
{
const char *orig_str = str_byte;
int first = tnfa->first_char;
if (len >= 0)
str_byte = memchr(orig_str, first, (size_t)len);
else
str_byte = strchr(orig_str, first);
if (str_byte == NULL)
{
#ifndef TRE_USE_ALLOCA
if (buf)
xfree(buf);
#endif /* !TRE_USE_ALLOCA */
return REG_NOMATCH;
}
DPRINT(("skipped %lu chars\n", (unsigned long)(str_byte - orig_str)));
if (str_byte >= orig_str + 1)
prev_c = (unsigned char)*(str_byte - 1);
next_c = (unsigned char)*str_byte;
pos = str_byte - orig_str;
if (len < 0 || pos < len)
str_byte++;
}
else
{
GET_NEXT_WCHAR();
pos = 0;
}
#if 0
/* Skip over characters that cannot possibly be the first character
of a match. */
if (tnfa->firstpos_chars != NULL)
{
char *chars = tnfa->firstpos_chars;
if (len < 0)
{
const char *orig_str = str_byte;
/* XXX - use strpbrk() and wcspbrk() because they might be
optimized for the target architecture. Try also strcspn()
and wcscspn() and compare the speeds. */
while (next_c != L'\0' && !chars[next_c])
{
next_c = *str_byte++;
}
prev_c = *(str_byte - 2);
pos += str_byte - orig_str;
DPRINT(("skipped %d chars\n", str_byte - orig_str));
}
else
{
while (pos <= len && !chars[next_c])
{
prev_c = next_c;
next_c = (unsigned char)(*str_byte++);
pos++;
}
}
}
#endif
DPRINT(("length: %zd\n", len));
DPRINT(("pos:chr/code | states and tags\n"));
DPRINT(("-------------+------------------------------------------------\n"));
reach_next_i = reach_next;
while (/*CONSTCOND*/(void)1,1)
{
/* If no match found yet, add the initial states to `reach_next'. */
if (match_eo < 0)
{
DPRINT((" init >"));
trans_i = tnfa->initial;
while (trans_i->state != NULL)
{
if (reach_pos[trans_i->state_id].pos < pos)
{
if (trans_i->assertions
&& CHECK_ASSERTIONS(trans_i->assertions))
{
DPRINT(("assertion failed\n"));
trans_i++;
continue;
}
DPRINT((" %p", (void *)trans_i->state));
reach_next_i->state = trans_i->state;
for (i = 0; i < num_tags; i++)
reach_next_i->tags[i] = -1;
tag_i = trans_i->tags;
if (tag_i)
while (*tag_i >= 0)
{
if (*tag_i < num_tags)
reach_next_i->tags[*tag_i] = pos;
tag_i++;
}
if (reach_next_i->state == tnfa->final)
{
DPRINT((" found empty match\n"));
match_eo = pos;
new_match = 1;
for (i = 0; i < num_tags; i++)
match_tags[i] = reach_next_i->tags[i];
}
reach_pos[trans_i->state_id].pos = pos;
reach_pos[trans_i->state_id].tags = &reach_next_i->tags;
reach_next_i++;
}
trans_i++;
}
DPRINT(("\n"));
reach_next_i->state = NULL;
}
else
{
if (num_tags == 0 || reach_next_i == reach_next)
/* We have found a match. */
break;
}
/* Check for end of string. */
if (len < 0)
{
if (type == STR_USER)
{
if (str_user_end)
break;
}
else if (next_c == L'\0' || pos >= TRE_MAX_STRING)
break;
}
else
{
if (pos >= len)
break;
}
GET_NEXT_WCHAR();
#ifdef TRE_DEBUG
DPRINT(("%3zd:%2lc/%05d |", pos - 1, (tre_cint_t)prev_c, (int)prev_c));
tre_print_reach(reach_next, num_tags);
DPRINT(("%3zd:%2lc/%05d |", pos, (tre_cint_t)next_c, (int)next_c));
tre_print_reach(reach_next, num_tags);
#endif /* TRE_DEBUG */
/* Swap `reach' and `reach_next'. */
reach_i = reach;
reach = reach_next;
reach_next = reach_i;
/* For each state in `reach', weed out states that don't fulfill the
minimal matching conditions. */
if (tnfa->num_minimals && new_match)
{
new_match = 0;
reach_next_i = reach_next;
for (reach_i = reach; reach_i->state; reach_i++)
{
int skip = 0;
for (i = 0; tnfa->minimal_tags[i] >= 0; i += 2)
{
int end = tnfa->minimal_tags[i];
int start = tnfa->minimal_tags[i + 1];
DPRINT((" Minimal start %d, end %d\n", start, end));
if (end >= num_tags)
{
DPRINT((" Throwing %p out.\n", reach_i->state));
skip = 1;
break;
}
else if (reach_i->tags[start] == match_tags[start]
&& reach_i->tags[end] < match_tags[end])
{
DPRINT((" Throwing %p out because t%d < %d\n",
reach_i->state, end, match_tags[end]));
skip = 1;
break;
}
}
if (!skip)
{
reach_next_i->state = reach_i->state;
tmp_iptr = reach_next_i->tags;
reach_next_i->tags = reach_i->tags;
reach_i->tags = tmp_iptr;
reach_next_i++;
}
}
reach_next_i->state = NULL;
/* Swap `reach' and `reach_next'. */
reach_i = reach;
reach = reach_next;
reach_next = reach_i;
}
/* For each state in `reach' see if there is a transition leaving with
the current input symbol to a state not yet in `reach_next', and
add the destination states to `reach_next'. */
reach_next_i = reach_next;
for (reach_i = reach; reach_i->state; reach_i++)
{
for (trans_i = reach_i->state; trans_i->state; trans_i++)
{
/* Does this transition match the input symbol? */
if (trans_i->code_min <= (tre_cint_t)prev_c &&
trans_i->code_max >= (tre_cint_t)prev_c)
{
if (trans_i->assertions
&& (CHECK_ASSERTIONS(trans_i->assertions)
|| CHECK_CHAR_CLASSES(trans_i, tnfa, eflags)))
{
DPRINT(("assertion failed\n"));
continue;
}
/* Compute the tags after this transition. */
for (i = 0; i < num_tags; i++)
tmp_tags[i] = reach_i->tags[i];
tag_i = trans_i->tags;
if (tag_i != NULL)
while (*tag_i >= 0)
{
if (*tag_i < num_tags)
tmp_tags[*tag_i] = pos;
tag_i++;
}
if (reach_pos[trans_i->state_id].pos < pos)
{
/* Found an unvisited node. */
reach_next_i->state = trans_i->state;
tmp_iptr = reach_next_i->tags;
reach_next_i->tags = tmp_tags;
tmp_tags = tmp_iptr;
reach_pos[trans_i->state_id].pos = pos;
reach_pos[trans_i->state_id].tags = &reach_next_i->tags;
if (reach_next_i->state == tnfa->final
&& (match_eo == -1
|| (num_tags > 0
&& reach_next_i->tags[0] <= match_tags[0])))
{
DPRINT((" found match %p\n", trans_i->state));
match_eo = pos;
new_match = 1;
for (i = 0; i < num_tags; i++)
match_tags[i] = reach_next_i->tags[i];
}
reach_next_i++;
}
else
{
assert(reach_pos[trans_i->state_id].pos == pos);
/* Another path has also reached this state. We choose
the winner by examining the tag values for both
paths. */
if (tre_tag_order(num_tags, tnfa->tag_directions,
tmp_tags,
*reach_pos[trans_i->state_id].tags))
{
/* The new path wins. */
tmp_iptr = *reach_pos[trans_i->state_id].tags;
*reach_pos[trans_i->state_id].tags = tmp_tags;
if (trans_i->state == tnfa->final)
{
DPRINT((" found better match\n"));
match_eo = pos;
new_match = 1;
for (i = 0; i < num_tags; i++)
match_tags[i] = tmp_tags[i];
}
tmp_tags = tmp_iptr;
}
}
}
}
}
reach_next_i->state = NULL;
}
DPRINT(("match end offset = %d\n", match_eo));
*match_end_ofs = match_eo;
ret = match_eo >= 0 ? REG_OK : REG_NOMATCH;
#ifndef TRE_USE_ALLOCA
if (buf)
xfree(buf);
#endif /* !TRE_USE_ALLOCA */
return ret;
}
/* EOF */
+215
View File
@@ -0,0 +1,215 @@
/*
tre-match-utils.h - TRE matcher helper definitions
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#define str_source ((const tre_str_source*)string)
#ifdef TRE_WCHAR
#ifdef TRE_MULTIBYTE
/* Wide character and multibyte support. */
#define GET_NEXT_WCHAR() \
do { \
prev_c = next_c; \
if (type == STR_BYTE) \
{ \
pos++; \
if (len >= 0 && pos >= len) \
next_c = '\0'; \
else \
next_c = (unsigned char)(*str_byte++); \
} \
else if (type == STR_WIDE) \
{ \
pos++; \
if (len >= 0 && pos >= len) \
next_c = L'\0'; \
else \
next_c = *str_wide++; \
} \
else if (type == STR_MBS) \
{ \
pos += pos_add_next; \
if (str_byte == NULL) \
next_c = L'\0'; \
else \
{ \
size_t w; \
size_t max; \
if (len >= 0) \
max = len - pos; \
else \
max = 32; \
if (max <= 0) \
{ \
next_c = L'\0'; \
pos_add_next = 1; \
} \
else \
{ \
w = tre_mbrtowc(&next_c, str_byte, (size_t)max, &mbstate); \
if (w == (size_t)-1 || w == (size_t)-2) \
return REG_NOMATCH; \
if (w == 0 && len >= 0) \
{ \
pos_add_next = 1; \
next_c = 0; \
str_byte++; \
} \
else \
{ \
pos_add_next = w; \
str_byte += w; \
} \
} \
} \
} \
else if (type == STR_USER) \
{ \
pos += pos_add_next; \
str_user_end = str_source->get_next_char(&next_c, &pos_add_next, \
str_source->context); \
} \
} while(/*CONSTCOND*/(void)0,0)
#else /* !TRE_MULTIBYTE */
/* Wide character support, no multibyte support. */
#define GET_NEXT_WCHAR() \
do { \
prev_c = next_c; \
if (type == STR_BYTE) \
{ \
pos++; \
if (len >= 0 && pos >= len) \
next_c = '\0'; \
else \
next_c = (unsigned char)(*str_byte++); \
} \
else if (type == STR_WIDE) \
{ \
pos++; \
if (len >= 0 && pos >= len) \
next_c = L'\0'; \
else \
next_c = *str_wide++; \
} \
else if (type == STR_USER) \
{ \
pos += pos_add_next; \
str_user_end = str_source->get_next_char(&next_c, &pos_add_next, \
str_source->context); \
} \
} while(/*CONSTCOND*/(void)0,0)
#endif /* !TRE_MULTIBYTE */
#else /* !TRE_WCHAR */
/* No wide character or multibyte support. */
#define GET_NEXT_WCHAR() \
do { \
prev_c = next_c; \
if (type == STR_BYTE) \
{ \
pos++; \
if (len >= 0 && pos >= len) \
next_c = '\0'; \
else \
next_c = (unsigned char)(*str_byte++); \
} \
else if (type == STR_USER) \
{ \
pos += pos_add_next; \
str_user_end = str_source->get_next_char(&next_c, &pos_add_next, \
str_source->context); \
} \
} while(/*CONSTCOND*/(void)0,0)
#endif /* !TRE_WCHAR */
#define IS_WORD_CHAR(c) ((c) == L'_' || tre_isalnum(c))
#define CHECK_ASSERTIONS(assertions) \
(((assertions & ASSERT_AT_BOL) \
&& (pos > 0 || reg_notbol) \
&& (prev_c != L'\n' || !reg_newline)) \
|| ((assertions & ASSERT_AT_EOL) \
&& (next_c != L'\0' || reg_noteol) \
&& (next_c != L'\n' || !reg_newline)) \
|| ((assertions & ASSERT_AT_BOW) \
&& (IS_WORD_CHAR(prev_c) || !IS_WORD_CHAR(next_c))) \
|| ((assertions & ASSERT_AT_EOW) \
&& (!IS_WORD_CHAR(prev_c) || IS_WORD_CHAR(next_c))) \
|| ((assertions & ASSERT_AT_WB) \
&& (pos != 0 && next_c != L'\0' \
&& IS_WORD_CHAR(prev_c) == IS_WORD_CHAR(next_c))) \
|| ((assertions & ASSERT_AT_WB_NEG) \
&& (pos == 0 || next_c == L'\0' \
|| IS_WORD_CHAR(prev_c) != IS_WORD_CHAR(next_c))))
#define CHECK_CHAR_CLASSES(trans_i, tnfa, eflags) \
(((trans_i->assertions & ASSERT_CHAR_CLASS) \
&& !(tnfa->cflags & REG_ICASE) \
&& !tre_isctype((tre_cint_t)prev_c, trans_i->u.class)) \
|| ((trans_i->assertions & ASSERT_CHAR_CLASS) \
&& (tnfa->cflags & REG_ICASE) \
&& !tre_isctype(tre_tolower((tre_cint_t)prev_c),trans_i->u.class) \
&& !tre_isctype(tre_toupper((tre_cint_t)prev_c),trans_i->u.class)) \
|| ((trans_i->assertions & ASSERT_CHAR_CLASS_NEG) \
&& tre_neg_char_classes_match(trans_i->neg_classes,(tre_cint_t)prev_c,\
tnfa->cflags & REG_ICASE)))
/* Returns 1 if `t1' wins `t2', 0 otherwise. */
inline static int
tre_tag_order(int num_tags, tre_tag_direction_t *tag_directions,
int *t1, int *t2)
{
int i;
for (i = 0; i < num_tags; i++)
{
if (tag_directions[i] == TRE_TAG_MINIMIZE)
{
if (t1[i] < t2[i])
return 1;
if (t1[i] > t2[i])
return 0;
}
else
{
if (t1[i] > t2[i])
return 1;
if (t1[i] < t2[i])
return 0;
}
}
/* assert(0);*/
return 0;
}
inline static int
tre_neg_char_classes_match(tre_ctype_t *classes, tre_cint_t wc, int icase)
{
DPRINT(("neg_char_classes_test: %p, %d, %d\n", classes, wc, icase));
while (*classes != (tre_ctype_t)0)
if ((!icase && tre_isctype(wc, *classes))
|| (icase && (tre_isctype(tre_toupper(wc), *classes)
|| tre_isctype(tre_tolower(wc), *classes))))
return 1; /* Match. */
else
classes++;
return 0; /* No match. */
}
+155
View File
@@ -0,0 +1,155 @@
/*
tre-mem.c - TRE memory allocator
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
/*
This memory allocator is for allocating small memory blocks efficiently
in terms of memory overhead and execution speed. The allocated blocks
cannot be freed individually, only all at once. There can be multiple
allocators, though.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */
#include <stdlib.h>
#include <string.h>
#include "tre-internal.h"
#include "tre-mem.h"
#include "xmalloc.h"
/* Returns a new memory allocator or NULL if out of memory. */
tre_mem_t
tre_mem_new_impl(int provided, void *provided_block)
{
tre_mem_t mem;
if (provided)
{
mem = provided_block;
memset(mem, 0, sizeof(*mem));
}
else
mem = xcalloc(1, sizeof(*mem));
if (mem == NULL)
return NULL;
return mem;
}
/* Frees the memory allocator and all memory allocated with it. */
void
tre_mem_destroy(tre_mem_t mem)
{
tre_list_t *tmp, *l = mem->blocks;
while (l != NULL)
{
xfree(l->data);
tmp = l->next;
xfree(l);
l = tmp;
}
xfree(mem);
}
/* Allocates a block of `size' bytes from `mem'. Returns a pointer to the
allocated block or NULL if an underlying malloc() failed. */
void *
tre_mem_alloc_impl(tre_mem_t mem, int provided, void *provided_block,
int zero, size_t size)
{
void *ptr;
if (mem->failed)
{
DPRINT(("tre_mem_alloc: oops, called after failure?!\n"));
return NULL;
}
#ifdef MALLOC_DEBUGGING
if (!provided)
{
ptr = xmalloc(1);
if (ptr == NULL)
{
DPRINT(("tre_mem_alloc: xmalloc forced failure\n"));
mem->failed = 1;
return NULL;
}
xfree(ptr);
}
#endif /* MALLOC_DEBUGGING */
if (mem->n < size)
{
/* We need more memory than is available in the current block.
Allocate a new block. */
tre_list_t *l;
if (provided)
{
DPRINT(("tre_mem_alloc: using provided block\n"));
if (provided_block == NULL)
{
DPRINT(("tre_mem_alloc: provided block was NULL\n"));
mem->failed = 1;
return NULL;
}
mem->ptr = provided_block;
mem->n = TRE_MEM_BLOCK_SIZE;
}
else
{
size_t block_size;
if (size * 8 > TRE_MEM_BLOCK_SIZE)
block_size = size * 8;
else
block_size = TRE_MEM_BLOCK_SIZE;
DPRINT(("tre_mem_alloc: allocating new %zu byte block\n",
block_size));
l = xmalloc(sizeof(*l));
if (l == NULL)
{
mem->failed = 1;
return NULL;
}
l->data = xmalloc(block_size);
if (l->data == NULL)
{
xfree(l);
mem->failed = 1;
return NULL;
}
l->next = NULL;
if (mem->current != NULL)
mem->current->next = l;
if (mem->blocks == NULL)
mem->blocks = l;
mem->current = l;
mem->ptr = l->data;
mem->n = block_size;
}
}
/* Make sure the next pointer will be aligned. */
size += ALIGN(mem->ptr + size, long);
/* Allocate from current block. */
ptr = mem->ptr;
mem->ptr += size;
mem->n -= size;
/* Set to zero if needed. */
if (zero)
memset(ptr, 0, size);
return ptr;
}
/* EOF */
+66
View File
@@ -0,0 +1,66 @@
/*
tre-mem.h - TRE memory allocator interface
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#ifndef TRE_MEM_H
#define TRE_MEM_H 1
#include <stdlib.h>
#define TRE_MEM_BLOCK_SIZE 1024
typedef struct tre_list {
void *data;
struct tre_list *next;
} tre_list_t;
typedef struct tre_mem_struct {
tre_list_t *blocks;
tre_list_t *current;
char *ptr;
size_t n;
int failed;
void **provided;
} *tre_mem_t;
tre_mem_t tre_mem_new_impl(int provided, void *provided_block);
void *tre_mem_alloc_impl(tre_mem_t mem, int provided, void *provided_block,
int zero, size_t size);
/* Returns a new memory allocator or NULL if out of memory. */
#define tre_mem_new() tre_mem_new_impl(0, NULL)
/* Allocates a block of `size' bytes from `mem'. Returns a pointer to the
allocated block or NULL if an underlying malloc() failed. */
#define tre_mem_alloc(mem, size) tre_mem_alloc_impl(mem, 0, NULL, 0, size)
/* Allocates a block of `size' bytes from `mem'. Returns a pointer to the
allocated block or NULL if an underlying malloc() failed. The memory
is set to zero. */
#define tre_mem_calloc(mem, size) tre_mem_alloc_impl(mem, 0, NULL, 1, size)
#ifdef TRE_USE_ALLOCA
/* alloca() versions. Like above, but memory is allocated with alloca()
instead of malloc(). */
#define tre_mem_newa() \
tre_mem_new_impl(1, alloca(sizeof(struct tre_mem_struct)))
#define tre_mem_alloca(mem, size) \
((mem)->n >= (size) \
? tre_mem_alloc_impl((mem), 1, NULL, 0, (size)) \
: tre_mem_alloc_impl((mem), 1, alloca(TRE_MEM_BLOCK_SIZE), 0, (size)))
#endif /* TRE_USE_ALLOCA */
/* Frees the memory allocator and all memory allocated with it. */
void tre_mem_destroy(tre_mem_t mem);
#endif /* TRE_MEM_H */
/* EOF */
+1758
View File
File diff suppressed because it is too large Load Diff
+52
View File
@@ -0,0 +1,52 @@
/*
tre-parse.c - Regexp parser definitions
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#ifndef TRE_PARSE_H
#define TRE_PARSE_H 1
/* Parse context. */
typedef struct {
/* Memory allocator. The AST is allocated using this. */
tre_mem_t mem;
/* Stack used for keeping track of regexp syntax. */
tre_stack_t *stack;
/* The parse result. */
tre_ast_node_t *result;
/* The regexp to parse and its length. */
const tre_char_t *re;
/* The first character of the entire regexp. */
const tre_char_t *re_start;
/* The first character after the end of the regexp. */
const tre_char_t *re_end;
size_t len;
/* Current submatch ID. */
int submatch_id;
/* The highest back reference or -1 if none seen so far. */
int max_backref;
/* This flag is set if the regexp uses approximate matching. */
int have_approx;
/* This flag is set if the regexp changes cflags inline using (?...) */
int have_inline_cflags;
/* Compilation flags. */
int cflags;
/* If this flag is set the top-level submatch is not captured. */
int nofirstsub;
/* The currently set approximate matching parameters. */
int params[TRE_PARAM_LAST];
/* the MB_CUR_MAX in use */
int mb_cur_max;
} tre_parse_ctx_t;
/* Parses a wide character regexp pattern into a syntax tree. This parser
handles both syntaxes (BRE and ERE), including the TRE extensions. */
reg_errcode_t
tre_parse(tre_parse_ctx_t *ctx);
#endif /* TRE_PARSE_H */
/* EOF */
+123
View File
@@ -0,0 +1,123 @@
/*
tre-stack.c - Simple stack implementation
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */
#include <stdlib.h>
#include <assert.h>
#include "tre-internal.h"
#include "tre-stack.h"
#include "xmalloc.h"
union tre_stack_item {
void *voidptr_value;
int int_value;
};
struct tre_stack_rec {
size_t size;
size_t max_size;
size_t ptr;
union tre_stack_item *stack;
};
tre_stack_t *
tre_stack_new(size_t size, size_t max_size)
{
tre_stack_t *s;
s = xmalloc(sizeof(*s));
if (s != NULL)
{
s->stack = xmalloc(sizeof(*s->stack) * size);
if (s->stack == NULL)
{
xfree(s);
return NULL;
}
s->size = size;
s->max_size = max_size;
s->ptr = 0;
}
return s;
}
void
tre_stack_destroy(tre_stack_t *s)
{
xfree(s->stack);
xfree(s);
}
size_t
tre_stack_num_items(tre_stack_t *s)
{
return s->ptr;
}
static reg_errcode_t
tre_stack_push(tre_stack_t *s, union tre_stack_item value)
{
if (s->ptr < s->size)
{
s->stack[s->ptr] = value;
s->ptr++;
}
else
{
if (s->size >= s->max_size)
{
DPRINT(("tre_stack_push: stack full\n"));
return REG_ESPACE;
}
else
{
union tre_stack_item *new_buffer;
size_t new_size;
DPRINT(("tre_stack_push: trying to realloc more space\n"));
new_size = s->size + s->size;
if (new_size > s->max_size)
new_size = s->max_size;
new_buffer = xrealloc(s->stack, sizeof(*new_buffer) * new_size);
if (new_buffer == NULL)
{
DPRINT(("tre_stack_push: realloc failed.\n"));
return REG_ESPACE;
}
DPRINT(("tre_stack_push: realloc succeeded.\n"));
assert(new_size > s->size);
s->size = new_size;
s->stack = new_buffer;
tre_stack_push(s, value);
}
}
return REG_OK;
}
#define define_pushf(typetag, type) \
declare_pushf(typetag, type) { \
union tre_stack_item item; \
item.typetag ## _value = value; \
return tre_stack_push(s, item); \
}
define_pushf(int, int)
define_pushf(voidptr, void *)
#define define_popf(typetag, type) \
declare_popf(typetag, type) { \
return s->stack[--s->ptr].typetag ## _value; \
}
define_popf(int, int)
define_popf(voidptr, void *)
/* EOF */
+76
View File
@@ -0,0 +1,76 @@
/*
tre-stack.h: Stack definitions
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#ifndef TRE_STACK_H
#define TRE_STACK_H 1
#include "../local_includes/tre.h"
typedef struct tre_stack_rec tre_stack_t;
/* Creates a new stack object with initial size `size' and maximum size
`max_size'. Pushing an additional item onto a full stack will resize
the stack to double its capacity until the maximum is reached. Returns
the stack object or NULL if out of memory. */
tre_stack_t *
tre_stack_new(size_t size, size_t max_size);
/* Frees the stack object. */
void
tre_stack_destroy(tre_stack_t *s);
/* Returns the current number of items on the stack. */
size_t
tre_stack_num_items(tre_stack_t *s);
/* Each tre_stack_push_*(tre_stack_t *s, <type> value) function pushes
`value' on top of stack `s'. Returns REG_ESPACE if out of memory.
This tries to realloc() more space before failing if maximum size
has not yet been reached. Returns REG_OK if successful. */
#define declare_pushf(typetag, type) \
reg_errcode_t tre_stack_push_ ## typetag(tre_stack_t *s, type value)
declare_pushf(voidptr, void *);
declare_pushf(int, int);
/* Each tre_stack_pop_*(tre_stack_t *s) function pops the topmost
element off of stack `s' and returns it. The stack must not be
empty. */
#define declare_popf(typetag, type) \
type tre_stack_pop_ ## typetag(tre_stack_t *s)
declare_popf(voidptr, void *);
declare_popf(int, int);
/* Just to save some typing. */
#define STACK_PUSH(s, typetag, value) \
do \
{ \
status = tre_stack_push_ ## typetag(s, value); \
} \
while (/*CONSTCOND*/(void)0,0)
#define STACK_PUSHX(s, typetag, value) \
{ \
status = tre_stack_push_ ## typetag(s, value); \
if (status != REG_OK) \
break; \
}
#define STACK_PUSHR(s, typetag, value) \
{ \
reg_errcode_t _status; \
_status = tre_stack_push_ ## typetag(s, value); \
if (_status != REG_OK) \
return _status; \
}
#endif /* TRE_STACK_H */
/* EOF */
+362
View File
@@ -0,0 +1,362 @@
/*
xmalloc.c - Simple malloc debugging library implementation
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
/*
TODO:
- red zones
- group dumps by source location
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */
#include <stdint.h>
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#define XMALLOC_INTERNAL 1
#include "xmalloc.h"
/*
Internal stuff.
*/
typedef struct hashTableItemRec {
void *ptr;
size_t bytes;
const char *file;
int line;
const char *func;
struct hashTableItemRec *next;
} hashTableItem;
typedef struct {
hashTableItem **table;
} hashTable;
static int xmalloc_peak;
int xmalloc_current;
static int xmalloc_peak_blocks;
int xmalloc_current_blocks;
static int xmalloc_fail_after;
#define TABLE_BITS 8
#define TABLE_MASK ((1 << TABLE_BITS) - 1)
#define TABLE_SIZE (1 << TABLE_BITS)
static hashTable *
hash_table_new(void)
{
hashTable *tbl;
tbl = malloc(sizeof(*tbl));
if (tbl != NULL)
{
tbl->table = calloc(TABLE_SIZE, sizeof(*tbl->table));
if (tbl->table == NULL)
{
free(tbl);
return NULL;
}
}
return tbl;
}
static unsigned int
hash_void_ptr(void *ptr)
{
unsigned int hash;
unsigned int i;
/* I took this hash function just off the top of my head, I have
no idea whether it is bad or very bad. */
hash = 0;
for (i = 0; i < sizeof(ptr) * 8 / TABLE_BITS; i++)
{
hash ^= (uintptr_t)ptr >> i * 8;
hash += i * 17;
hash &= TABLE_MASK;
}
return hash;
}
static void
hash_table_add(hashTable *tbl, void *ptr, size_t bytes,
const char *file, int line, const char *func)
{
unsigned int i;
hashTableItem *item, *new;
i = hash_void_ptr(ptr);
item = tbl->table[i];
if (item != NULL)
while (item->next != NULL)
item = item->next;
new = malloc(sizeof(*new));
assert(new != NULL);
new->ptr = ptr;
new->bytes = bytes;
new->file = file;
new->line = line;
new->func = func;
new->next = NULL;
if (item != NULL)
item->next = new;
else
tbl->table[i] = new;
xmalloc_current += bytes;
if (xmalloc_current > xmalloc_peak)
xmalloc_peak = xmalloc_current;
xmalloc_current_blocks++;
if (xmalloc_current_blocks > xmalloc_peak_blocks)
xmalloc_peak_blocks = xmalloc_current_blocks;
}
static void
#if defined(__GNUC__) && __GNUC__ >= 11
__attribute__((access(none, 2)))
#endif
hash_table_del(hashTable *tbl, void *ptr)
{
int i;
hashTableItem *item, *prev;
i = hash_void_ptr(ptr);
item = tbl->table[i];
if (item == NULL)
{
printf("xfree: invalid ptr %p\n", ptr);
abort();
}
prev = NULL;
while (item->ptr != ptr)
{
prev = item;
item = item->next;
}
if (item->ptr != ptr)
{
printf("xfree: invalid ptr %p\n", ptr);
abort();
}
xmalloc_current -= item->bytes;
xmalloc_current_blocks--;
if (prev != NULL)
{
prev->next = item->next;
free(item);
}
else
{
tbl->table[i] = item->next;
free(item);
}
}
static hashTable *xmalloc_table = NULL;
static void
xmalloc_init(void)
{
if (xmalloc_table == NULL)
{
xmalloc_table = hash_table_new();
xmalloc_peak = 0;
xmalloc_peak_blocks = 0;
xmalloc_current = 0;
xmalloc_current_blocks = 0;
xmalloc_fail_after = -1;
}
assert(xmalloc_table != NULL);
assert(xmalloc_table->table != NULL);
}
/*
Public API.
*/
void
xmalloc_configure(int fail_after)
{
xmalloc_init();
xmalloc_fail_after = fail_after;
}
int
xmalloc_dump_leaks(void)
{
unsigned int i;
unsigned int num_leaks = 0;
size_t leaked_bytes = 0;
hashTableItem *item;
xmalloc_init();
for (i = 0; i < TABLE_SIZE; i++)
{
item = xmalloc_table->table[i];
while (item != NULL)
{
printf("%s:%d: %s: %zu bytes at %p not freed\n",
item->file, item->line, item->func, item->bytes, item->ptr);
num_leaks++;
leaked_bytes += item->bytes;
item = item->next;
}
}
if (num_leaks == 0)
printf("No memory leaks.\n");
else
printf("%u unfreed memory chuncks, total %zu unfreed bytes.\n",
num_leaks, leaked_bytes);
printf("Peak memory consumption %d bytes (%.1f kB, %.1f MB) in %d blocks ",
xmalloc_peak, (double)xmalloc_peak / 1024,
(double)xmalloc_peak / (1024*1024), xmalloc_peak_blocks);
printf("(average ");
if (xmalloc_peak_blocks)
printf("%d", ((xmalloc_peak + xmalloc_peak_blocks / 2)
/ xmalloc_peak_blocks));
else
printf("N/A");
printf(" bytes per block).\n");
return num_leaks;
}
void *
xmalloc_impl(size_t size, const char *file, int line, const char *func)
{
void *ptr;
xmalloc_init();
assert(size > 0);
if (xmalloc_fail_after == 0)
{
xmalloc_fail_after = -2;
#if 0
printf("xmalloc: forced failure %s:%d: %s\n", file, line, func);
#endif
return NULL;
}
else if (xmalloc_fail_after == -2)
{
printf("xmalloc: called after failure from %s:%d: %s\n",
file, line, func);
assert(0);
}
else if (xmalloc_fail_after > 0)
xmalloc_fail_after--;
ptr = malloc(size);
if (ptr != NULL)
hash_table_add(xmalloc_table, ptr, (int)size, file, line, func);
return ptr;
}
void *
xcalloc_impl(size_t nmemb, size_t size, const char *file, int line,
const char *func)
{
void *ptr;
xmalloc_init();
assert(size > 0);
if (xmalloc_fail_after == 0)
{
xmalloc_fail_after = -2;
#if 0
printf("xcalloc: forced failure %s:%d: %s\n", file, line, func);
#endif
return NULL;
}
else if (xmalloc_fail_after == -2)
{
printf("xcalloc: called after failure from %s:%d: %s\n",
file, line, func);
assert(0);
}
else if (xmalloc_fail_after > 0)
xmalloc_fail_after--;
ptr = calloc(nmemb, size);
if (ptr != NULL)
hash_table_add(xmalloc_table, ptr, (int)(nmemb * size), file, line, func);
return ptr;
}
void
xfree_impl(void *ptr, const char *file, int line, const char *func)
{
/*LINTED*/(void)&file;
/*LINTED*/(void)&line;
/*LINTED*/(void)&func;
xmalloc_init();
if (ptr != NULL)
hash_table_del(xmalloc_table, ptr);
free(ptr);
}
void *
xrealloc_impl(void *ptr, size_t new_size, const char *file, int line,
const char *func)
{
void *new_ptr;
xmalloc_init();
assert(ptr != NULL);
assert(new_size > 0);
if (xmalloc_fail_after == 0)
{
xmalloc_fail_after = -2;
return NULL;
}
else if (xmalloc_fail_after == -2)
{
printf("xrealloc: called after failure from %s:%d: %s\n",
file, line, func);
assert(0);
}
else if (xmalloc_fail_after > 0)
xmalloc_fail_after--;
new_ptr = realloc(ptr, new_size);
if (new_ptr != NULL && new_ptr != ptr)
{
#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ >= 12
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wuse-after-free"
#endif
hash_table_del(xmalloc_table, ptr);
#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ >= 12
#pragma GCC diagnostic pop
#endif
hash_table_add(xmalloc_table, new_ptr, (int)new_size, file, line, func);
}
return new_ptr;
}
/* EOF */
+77
View File
@@ -0,0 +1,77 @@
/*
xmalloc.h - Simple malloc debugging library API
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#ifndef _XMALLOC_H
#define _XMALLOC_H 1
void *xmalloc_impl(size_t size, const char *file, int line, const char *func);
void *xcalloc_impl(size_t nmemb, size_t size, const char *file, int line,
const char *func);
void xfree_impl(void *ptr, const char *file, int line, const char *func);
void *xrealloc_impl(void *ptr, size_t new_size, const char *file, int line,
const char *func);
int xmalloc_dump_leaks(void);
void xmalloc_configure(int fail_after);
#ifndef XMALLOC_INTERNAL
#ifdef MALLOC_DEBUGGING
/* Version 2.4 and later of GCC define a magical variable `__PRETTY_FUNCTION__'
which contains the name of the function currently being defined.
# define __XMALLOC_FUNCTION __PRETTY_FUNCTION__
This is broken in G++ before version 2.6.
C9x has a similar variable called __func__, but prefer the GCC one since
it demangles C++ function names. */
# ifdef __GNUC__
# if __GNUC__ > 2 || (__GNUC__ == 2 \
&& __GNUC_MINOR__ >= (defined __cplusplus ? 6 : 4))
# define __XMALLOC_FUNCTION __PRETTY_FUNCTION__
# else
# define __XMALLOC_FUNCTION ((const char *) 0)
# endif
# else
# if defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L
# define __XMALLOC_FUNCTION __func__
# else
# define __XMALLOC_FUNCTION ((const char *) 0)
# endif
# endif
#define xmalloc(size) xmalloc_impl(size, __FILE__, __LINE__, \
__XMALLOC_FUNCTION)
#define xcalloc(nmemb, size) xcalloc_impl(nmemb, size, __FILE__, __LINE__, \
__XMALLOC_FUNCTION)
#define xfree(ptr) xfree_impl(ptr, __FILE__, __LINE__, __XMALLOC_FUNCTION)
#define xrealloc(ptr, new_size) xrealloc_impl(ptr, new_size, __FILE__, \
__LINE__, __XMALLOC_FUNCTION)
#undef malloc
#undef calloc
#undef free
#undef realloc
#define malloc USE_XMALLOC_INSTEAD_OF_MALLOC
#define calloc USE_XCALLOC_INSTEAD_OF_CALLOC
#define free USE_XFREE_INSTEAD_OF_FREE
#define realloc USE_XREALLOC_INSTEAD_OF_REALLOC
#else /* !MALLOC_DEBUGGING */
#include <stdlib.h>
#define xmalloc(size) malloc(size)
#define xcalloc(nmemb, size) calloc(nmemb, size)
#define xfree(ptr) free(ptr)
#define xrealloc(ptr, new_size) realloc(ptr, new_size)
#endif /* !MALLOC_DEBUGGING */
#endif /* !XMALLOC_INTERNAL */
#endif /* _XMALLOC_H */
/* EOF */
+48
View File
@@ -0,0 +1,48 @@
/*
regex.h - TRE legacy API
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
This header is for source level compatibility with old code using
the <tre/regex.h> header which defined the TRE API functions without
a prefix. New code should include <tre/tre.h> instead.
*/
#ifndef TRE_REXEX_H
#define TRE_REGEX_H 1
#ifdef USE_LOCAL_TRE_H
/* Use the header(s) from the TRE package that this file is part of.
(Yes, this file is in local_include too, but the explict path
means there is no way to get a system tre.h by accident.) */
#include "../local_includes/tre.h"
#else
/* Use the header(s) from an installed version of the TRE package
(so that this application matches the installed libtre),
not the one(s) in the local_includes directory. */
#include <tre/tre.h>
#endif
#ifndef TRE_USE_SYSTEM_REGEX_H
#define regcomp tre_regcomp
#define regerror tre_regerror
#define regexec tre_regexec
#define regfree tre_regfree
#endif /* TRE_USE_SYSTEM_REGEX_H */
#define regacomp tre_regacomp
#define regaexec tre_regaexec
#define regancomp tre_regancomp
#define reganexec tre_reganexec
#define regawncomp tre_regawncomp
#define regawnexec tre_regawnexec
#define regncomp tre_regncomp
#define regnexec tre_regnexec
#define regwcomp tre_regwcomp
#define regwexec tre_regwexec
#define regwncomp tre_regwncomp
#define regwnexec tre_regwnexec
#endif /* TRE_REGEX_H */
+14
View File
@@ -0,0 +1,14 @@
/* Minimal TRE configuration for Redis.
*
* We use TRE as a byte-oriented regex matcher for ARGREP. Redis SDS values are
* binary-safe byte strings, so we intentionally keep the dependency build
* simple: no wide-char path, no multibyte locale handling, and no approximate
* matching engine.
*/
#define HAVE_SYS_TYPES_H 1
#define TRE_VERSION "redis-vendored"
#define TRE_VERSION_1 0
#define TRE_VERSION_2 0
#define TRE_VERSION_3 0
+344
View File
@@ -0,0 +1,344 @@
/*
tre.h - TRE public API definitions
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#ifndef TRE_H
#define TRE_H 1
#ifdef USE_LOCAL_TRE_H
/* Make certain to use the header(s) from the TRE package that this
file is part of by giving the full path to the header from this directory. */
#include "../local_includes/tre-config.h"
#else
/* Use the header in the same directory as this file if there is one. */
#include "tre-config.h"
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif /* HAVE_SYS_TYPES_H */
#ifdef HAVE_LIBUTF8_H
#include <libutf8.h>
#endif /* HAVE_LIBUTF8_H */
#ifdef TRE_USE_SYSTEM_REGEX_H
/* Include the system regex.h to make TRE ABI compatible with the
system regex. */
#include TRE_SYSTEM_REGEX_H_PATH
#define tre_regcomp regcomp
#define tre_regexec regexec
#define tre_regerror regerror
#define tre_regfree regfree
/* The GNU C regex has a number of refinements to the POSIX standard for the
formal parameter list of the regexec() function, and some of those fail to
compile when using LLVM. The refinements seem to be opt-out rather than
opt-in when using a recent gcc, and they produce a warning when TRE tries
to mimic the API without the refinements. The TRE code still works but
the warnings are distracting, so try to #define a flag to indicate when to
add the refinements to TRE's parameter list too. */
#ifdef __GNUC__
/* Try to test something that looks pretty REGEX specific and hope we don't
need a zillion different platform+compiler specific tests to deal with this. */
#ifdef _REGEX_NELTS
/* Define a TRE specific flag here so that:
1) there is only one place where code has to be changed if the test above is not adequate, and
2) the flag can be used in any other parts of the TRE source that might be affected by the
GNUC refinements.
Note that this flag is only defined when all of TRE_USE_SYSTEM_REGEX_H, __GNUC__, and _REGEX_NELTS are defined. */
#define TRE_USE_GNUC_REGEXEC_FPL 1
#endif
#endif
#endif /* TRE_USE_SYSTEM_REGEX_H */
#ifdef __cplusplus
extern "C" {
#endif
#ifdef TRE_USE_SYSTEM_REGEX_H
#ifndef REG_OK
#define REG_OK 0
#endif /* !REG_OK */
#ifndef HAVE_REG_ERRCODE_T
typedef int reg_errcode_t;
#endif /* !HAVE_REG_ERRCODE_T */
#if !defined(REG_NOSPEC) && !defined(REG_LITERAL)
#define REG_LITERAL 0x1000
#endif
/* Extra tre_regcomp() return error codes. */
#define REG_BADMAX REG_BADBR
/* Extra tre_regcomp() flags. */
#ifndef REG_BASIC
#define REG_BASIC 0
#endif /* !REG_BASIC */
#define REG_RIGHT_ASSOC (REG_LITERAL << 1)
#ifdef REG_UNGREEDY
/* We're going to use TRE code, so we need the TRE define (dodge problem in MacOS). */
#undef REG_UNGREEDY
#endif
#define REG_UNGREEDY (REG_RIGHT_ASSOC << 1)
#define REG_USEBYTES (REG_UNGREEDY << 1)
/* Extra tre_regexec() flags. */
#define REG_APPROX_MATCHER 0x1000
#ifdef REG_BACKTRACKING_MATCHER
/* We're going to use TRE code, so we need the TRE define (dodge problem in MacOS). */
#undef REG_BACKTRACKING_MATCHER
#endif
#define REG_BACKTRACKING_MATCHER (REG_APPROX_MATCHER << 1)
#else /* !TRE_USE_SYSTEM_REGEX_H */
/* If the we're not using system regex.h, we need to define the
structs and enums ourselves. */
typedef int regoff_t;
typedef struct {
size_t re_nsub; /* Number of parenthesized subexpressions. */
void *value; /* For internal use only. */
} regex_t;
typedef struct {
regoff_t rm_so;
regoff_t rm_eo;
} regmatch_t;
typedef enum {
REG_OK = 0, /* No error. */
/* POSIX tre_regcomp() return error codes. (In the order listed in the
standard.) */
REG_NOMATCH, /* No match. */
REG_BADPAT, /* Invalid regexp. */
REG_ECOLLATE, /* Unknown collating element. */
REG_ECTYPE, /* Unknown character class name. */
REG_EESCAPE, /* Trailing backslash. */
REG_ESUBREG, /* Invalid back reference. */
REG_EBRACK, /* "[]" imbalance */
REG_EPAREN, /* "\(\)" or "()" imbalance */
REG_EBRACE, /* "\{\}" or "{}" imbalance */
REG_BADBR, /* Invalid content of {} */
REG_ERANGE, /* Invalid use of range operator */
REG_ESPACE, /* Out of memory. */
REG_BADRPT, /* Invalid use of repetition operators. */
REG_BADMAX, /* Maximum repetition in {} too large */
} reg_errcode_t;
/* POSIX tre_regcomp() flags. */
#define REG_EXTENDED 1
#define REG_ICASE (REG_EXTENDED << 1)
#define REG_NEWLINE (REG_ICASE << 1)
#define REG_NOSUB (REG_NEWLINE << 1)
/* Extra tre_regcomp() flags. */
#define REG_BASIC 0
#define REG_LITERAL (REG_NOSUB << 1)
#define REG_RIGHT_ASSOC (REG_LITERAL << 1)
#define REG_UNGREEDY (REG_RIGHT_ASSOC << 1)
#define REG_USEBYTES (REG_UNGREEDY << 1)
/* POSIX tre_regexec() flags. */
#define REG_NOTBOL 1
#define REG_NOTEOL (REG_NOTBOL << 1)
/* Extra tre_regexec() flags. */
#define REG_APPROX_MATCHER (REG_NOTEOL << 1)
#define REG_BACKTRACKING_MATCHER (REG_APPROX_MATCHER << 1)
#endif /* !TRE_USE_SYSTEM_REGEX_H */
/* REG_NOSPEC and REG_LITERAL mean the same thing. */
#if defined(REG_LITERAL) && !defined(REG_NOSPEC)
#define REG_NOSPEC REG_LITERAL
#elif defined(REG_NOSPEC) && !defined(REG_LITERAL)
#define REG_LITERAL REG_NOSPEC
#endif /* defined(REG_NOSPEC) */
/* The maximum number of iterations in a bound expression. */
#undef RE_DUP_MAX
#define RE_DUP_MAX 255
/* The POSIX.2 regexp functions */
extern int
tre_regcomp(regex_t *preg, const char *regex, int cflags);
#ifdef TRE_USE_GNUC_REGEXEC_FPL
extern int
tre_regexec(const regex_t *preg, const char *string,
size_t nmatch, regmatch_t pmatch[_Restrict_arr_ _REGEX_NELTS (nmatch)],
int eflags);
#else
extern int
tre_regexec(const regex_t *preg, const char *string, size_t nmatch,
regmatch_t pmatch[], int eflags);
#endif
extern int
tre_regcompb(regex_t *preg, const char *regex, int cflags);
extern int
tre_regexecb(const regex_t *preg, const char *string, size_t nmatch,
regmatch_t pmatch[], int eflags);
extern size_t
tre_regerror(int errcode, const regex_t *preg, char *errbuf,
size_t errbuf_size);
extern void
tre_regfree(regex_t *preg);
#ifdef TRE_WCHAR
#ifdef HAVE_WCHAR_H
#include <wchar.h>
#endif /* HAVE_WCHAR_H */
/* Wide character versions (not in POSIX.2). */
extern int
tre_regwcomp(regex_t *preg, const wchar_t *regex, int cflags);
extern int
tre_regwexec(const regex_t *preg, const wchar_t *string,
size_t nmatch, regmatch_t pmatch[], int eflags);
#endif /* TRE_WCHAR */
/* Versions with a maximum length argument and therefore the capability to
handle null characters in the middle of the strings (not in POSIX.2). */
extern int
tre_regncomp(regex_t *preg, const char *regex, size_t len, int cflags);
extern int
tre_regnexec(const regex_t *preg, const char *string, size_t len,
size_t nmatch, regmatch_t pmatch[], int eflags);
/* regn*b versions take byte literally as 8-bit values */
extern int
tre_regncompb(regex_t *preg, const char *regex, size_t n, int cflags);
extern int
tre_regnexecb(const regex_t *preg, const char *str, size_t len,
size_t nmatch, regmatch_t pmatch[], int eflags);
#ifdef TRE_WCHAR
extern int
tre_regwncomp(regex_t *preg, const wchar_t *regex, size_t len, int cflags);
extern int
tre_regwnexec(const regex_t *preg, const wchar_t *string, size_t len,
size_t nmatch, regmatch_t pmatch[], int eflags);
#endif /* TRE_WCHAR */
#ifdef TRE_APPROX
/* Approximate matching parameter struct. */
typedef struct {
int cost_ins; /* Default cost of an inserted character. */
int cost_del; /* Default cost of a deleted character. */
int cost_subst; /* Default cost of a substituted character. */
int max_cost; /* Maximum allowed cost of a match. */
int max_ins; /* Maximum allowed number of inserts. */
int max_del; /* Maximum allowed number of deletes. */
int max_subst; /* Maximum allowed number of substitutes. */
int max_err; /* Maximum allowed number of errors total. */
} regaparams_t;
/* Approximate matching result struct. */
typedef struct {
size_t nmatch; /* Length of pmatch[] array. */
regmatch_t *pmatch; /* Submatch data. */
int cost; /* Cost of the match. */
int num_ins; /* Number of inserts in the match. */
int num_del; /* Number of deletes in the match. */
int num_subst; /* Number of substitutes in the match. */
} regamatch_t;
/* Approximate matching functions. */
extern int
tre_regaexec(const regex_t *preg, const char *string,
regamatch_t *match, regaparams_t params, int eflags);
extern int
tre_reganexec(const regex_t *preg, const char *string, size_t len,
regamatch_t *match, regaparams_t params, int eflags);
extern int
tre_regaexecb(const regex_t *preg, const char *string,
regamatch_t *match, regaparams_t params, int eflags);
#ifdef TRE_WCHAR
/* Wide character approximate matching. */
extern int
tre_regawexec(const regex_t *preg, const wchar_t *string,
regamatch_t *match, regaparams_t params, int eflags);
extern int
tre_regawnexec(const regex_t *preg, const wchar_t *string, size_t len,
regamatch_t *match, regaparams_t params, int eflags);
#endif /* TRE_WCHAR */
/* Sets the parameters to default values. */
extern void
tre_regaparams_default(regaparams_t *params);
#endif /* TRE_APPROX */
#ifdef TRE_WCHAR
typedef wchar_t tre_char_t;
#else /* !TRE_WCHAR */
typedef unsigned char tre_char_t;
#endif /* !TRE_WCHAR */
typedef struct {
int (*get_next_char)(tre_char_t *c, unsigned int *pos_add, void *context);
void (*rewind)(size_t pos, void *context);
int (*compare)(size_t pos1, size_t pos2, size_t len, void *context);
void *context;
} tre_str_source;
extern int
tre_reguexec(const regex_t *preg, const tre_str_source *string,
size_t nmatch, regmatch_t pmatch[], int eflags);
/* Returns the version string. The returned string is static. */
extern char *
tre_version(void);
/* Returns the value for a config parameter. The type to which `result'
must point to depends of the value of `query', see documentation for
more details. */
extern int
tre_config(int query, void *result);
enum {
TRE_CONFIG_APPROX,
TRE_CONFIG_WCHAR,
TRE_CONFIG_MULTIBYTE,
TRE_CONFIG_SYSTEM_ABI,
TRE_CONFIG_VERSION
};
/* Returns 1 if the compiled pattern has back references, 0 if not. */
extern int
tre_have_backrefs(const regex_t *preg);
/* Returns 1 if the compiled pattern uses approximate matching features,
0 if not. */
extern int
tre_have_approx(const regex_t *preg);
#ifdef __cplusplus
}
#endif
#endif /* TRE_H */
/* EOF */
+1871
View File
File diff suppressed because it is too large Load Diff
+303
View File
@@ -0,0 +1,303 @@
/*
test-literal-opt.c - Validate TRE literal optimization against the
generic matcher.
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */
#include <locale.h>
#include <stdio.h>
#include <string.h>
#include "tre-internal.h"
#define PMATCH_SLOTS 4
#define RC_ANY -9999
typedef struct {
const char *name;
const char *pattern;
size_t pattern_len;
int cflags;
const char *string;
size_t string_len;
int eflags;
int expected_rc;
tre_literal_opt_mode_t expected_mode;
} litopt_case_t;
static void
init_pmatch(regmatch_t pmatch[], size_t count)
{
size_t i;
for (i = 0; i < count; i++)
{
pmatch[i].rm_so = 111;
pmatch[i].rm_eo = 222;
}
}
static int
same_pmatch(const regmatch_t a[], const regmatch_t b[], size_t count)
{
size_t i;
for (i = 0; i < count; i++)
if (a[i].rm_so != b[i].rm_so || a[i].rm_eo != b[i].rm_eo)
return 0;
return 1;
}
static int
pmatch_cleared(const regmatch_t pmatch[], size_t count)
{
size_t i;
for (i = 0; i < count; i++)
if (pmatch[i].rm_so != -1 || pmatch[i].rm_eo != -1)
return 0;
return 1;
}
static int
run_case(const litopt_case_t *tc)
{
regex_t preg;
tre_tnfa_t *tnfa;
regmatch_t fast[PMATCH_SLOTS], slow[PMATCH_SLOTS];
tre_literal_opt_mode_t saved_mode;
char errbuf[256];
int errcode, fast_rc, slow_rc;
memset(&preg, 0, sizeof(preg));
errcode = tre_regncompb(&preg, tc->pattern, tc->pattern_len, tc->cflags);
if (errcode != REG_OK)
{
tre_regerror(errcode, &preg, errbuf, sizeof(errbuf));
fprintf(stderr, "%s: compile failed: %s\n", tc->name, errbuf);
return 1;
}
tnfa = (tre_tnfa_t *)preg.value;
if (tnfa->literal_opt.mode != tc->expected_mode)
{
fprintf(stderr, "%s: optimizer mode %d, expected %d\n",
tc->name, (int)tnfa->literal_opt.mode, (int)tc->expected_mode);
tre_regfree(&preg);
return 1;
}
init_pmatch(fast, PMATCH_SLOTS);
init_pmatch(slow, PMATCH_SLOTS);
fast_rc = tre_regnexecb(&preg, tc->string, tc->string_len,
PMATCH_SLOTS, fast, tc->eflags);
saved_mode = tnfa->literal_opt.mode;
tnfa->literal_opt.mode = TRE_LITERAL_OPT_NONE;
slow_rc = tre_regnexecb(&preg, tc->string, tc->string_len,
PMATCH_SLOTS, slow, tc->eflags);
tnfa->literal_opt.mode = saved_mode;
if (fast_rc != slow_rc)
{
fprintf(stderr, "%s: fast rc %d, slow rc %d\n",
tc->name, fast_rc, slow_rc);
tre_regfree(&preg);
return 1;
}
if (tc->expected_rc != RC_ANY && fast_rc != tc->expected_rc)
{
fprintf(stderr, "%s: rc %d, expected %d\n",
tc->name, fast_rc, tc->expected_rc);
tre_regfree(&preg);
return 1;
}
if (!same_pmatch(fast, slow, PMATCH_SLOTS))
{
fprintf(stderr, "%s: fast and slow pmatch differ\n", tc->name);
tre_regfree(&preg);
return 1;
}
if ((tc->cflags & REG_NOSUB) && fast_rc == REG_OK
&& !pmatch_cleared(fast, PMATCH_SLOTS))
{
fprintf(stderr, "%s: REG_NOSUB match did not clear pmatch\n", tc->name);
tre_regfree(&preg);
return 1;
}
tre_regfree(&preg);
return 0;
}
int
main(void)
{
static const char nonascii_pattern[] = { (char)0xc0, '|', (char)0xe0 };
static const char nonascii_haystack[] = { 'x', (char)0xe0, 'y' };
static const litopt_case_t cases[] = {
{
"contains basic",
"foo|bar|baz",
sizeof("foo|bar|baz") - 1,
REG_EXTENDED | REG_NOSUB,
"xxbaryy",
sizeof("xxbaryy") - 1,
0,
REG_OK,
TRE_LITERAL_OPT_CONTAINS
},
{
"contains ignores bol/eol flags",
"foo|bar|baz",
sizeof("foo|bar|baz") - 1,
REG_EXTENDED | REG_NOSUB,
"xxbaryy",
sizeof("xxbaryy") - 1,
REG_NOTBOL | REG_NOTEOL,
REG_OK,
TRE_LITERAL_OPT_CONTAINS
},
{
"prefix basic",
"^(foo|bar|baz)",
sizeof("^(foo|bar|baz)") - 1,
REG_EXTENDED | REG_NOSUB,
"barrier",
sizeof("barrier") - 1,
0,
REG_OK,
TRE_LITERAL_OPT_PREFIX
},
{
"prefix respects REG_NOTBOL",
"^(foo|bar|baz)",
sizeof("^(foo|bar|baz)") - 1,
REG_EXTENDED | REG_NOSUB,
"barrier",
sizeof("barrier") - 1,
REG_NOTBOL,
REG_NOMATCH,
TRE_LITERAL_OPT_PREFIX
},
{
"suffix basic",
"(foo|bar|baz)$",
sizeof("(foo|bar|baz)$") - 1,
REG_EXTENDED | REG_NOSUB,
"crowbar",
sizeof("crowbar") - 1,
0,
REG_OK,
TRE_LITERAL_OPT_SUFFIX
},
{
"suffix respects REG_NOTEOL",
"(foo|bar|baz)$",
sizeof("(foo|bar|baz)$") - 1,
REG_EXTENDED | REG_NOSUB,
"crowbar",
sizeof("crowbar") - 1,
REG_NOTEOL,
REG_NOMATCH,
TRE_LITERAL_OPT_SUFFIX
},
{
"exact basic",
"^(foo|bar|baz)$",
sizeof("^(foo|bar|baz)$") - 1,
REG_EXTENDED | REG_NOSUB,
"bar",
sizeof("bar") - 1,
0,
REG_OK,
TRE_LITERAL_OPT_EXACT
},
{
"exact respects REG_NOTBOL",
"^(foo|bar|baz)$",
sizeof("^(foo|bar|baz)$") - 1,
REG_EXTENDED | REG_NOSUB,
"bar",
sizeof("bar") - 1,
REG_NOTBOL,
REG_NOMATCH,
TRE_LITERAL_OPT_EXACT
},
{
"exact respects REG_NOTEOL",
"^(foo|bar|baz)$",
sizeof("^(foo|bar|baz)$") - 1,
REG_EXTENDED | REG_NOSUB,
"bar",
sizeof("bar") - 1,
REG_NOTEOL,
REG_NOMATCH,
TRE_LITERAL_OPT_EXACT
},
{
"empty alternation disables optimization",
"(|foo|bar)",
sizeof("(|foo|bar)") - 1,
REG_EXTENDED | REG_NOSUB,
"",
0,
0,
REG_OK,
TRE_LITERAL_OPT_NONE
},
{
"inline flag disable stays generic",
"foo(?-i:zap)zot",
sizeof("foo(?-i:zap)zot") - 1,
REG_EXTENDED | REG_ICASE | REG_NOSUB,
"FoOzApZOt",
sizeof("FoOzApZOt") - 1,
0,
REG_NOMATCH,
TRE_LITERAL_OPT_NONE
},
{
"inline flag disable still matches exact scoped bytes",
"foo(?-i:zap)zot",
sizeof("foo(?-i:zap)zot") - 1,
REG_EXTENDED | REG_ICASE | REG_NOSUB,
"FoOzapZOt",
sizeof("FoOzapZOt") - 1,
0,
REG_OK,
TRE_LITERAL_OPT_NONE
},
{
"nocase non-ascii bytes stay in sync",
nonascii_pattern,
sizeof(nonascii_pattern),
REG_EXTENDED | REG_ICASE | REG_NOSUB,
nonascii_haystack,
sizeof(nonascii_haystack),
0,
RC_ANY,
TRE_LITERAL_OPT_CONTAINS
}
};
size_t i;
int failures = 0;
setlocale(LC_CTYPE, "en_US.ISO-8859-1");
for (i = 0; i < elementsof(cases); i++)
failures += run_case(&cases[i]);
return failures;
}
+85
View File
@@ -0,0 +1,85 @@
/*
test-malformed-regn.c - Verify exact-length edge-case regexps compile or fail
cleanly both with and without a trailing NUL byte.
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "tre.h"
typedef struct {
const char *name;
const char *pattern;
int expected_err;
} malformed_case_t;
static int
run_case(const malformed_case_t *tc, int nul_terminated)
{
regex_t preg;
size_t len = strlen(tc->pattern);
size_t alloc_len = len + (nul_terminated ? 1 : 0);
char *pattern = malloc(alloc_len ? alloc_len : 1);
int errcode;
if (pattern == NULL)
{
fprintf(stderr, "%s: out of memory\n", tc->name);
return 1;
}
if (len > 0)
memcpy(pattern, tc->pattern, len);
if (nul_terminated)
pattern[len] = '\0';
memset(&preg, 0, sizeof(preg));
errcode = tre_regncompb(&preg, pattern, len, REG_EXTENDED | REG_NOSUB);
if (errcode == REG_OK)
tre_regfree(&preg);
free(pattern);
if (errcode != tc->expected_err)
{
char errbuf[128];
memset(&preg, 0, sizeof(preg));
tre_regerror(errcode, &preg, errbuf, sizeof(errbuf));
fprintf(stderr, "%s (%s): got %d (%s), expected %d\n",
tc->name, nul_terminated ? "nul" : "exact",
errcode, errbuf, tc->expected_err);
return 1;
}
return 0;
}
int
main(void)
{
static const malformed_case_t cases[] = {
{ "open paren", "(", REG_EPAREN },
{ "open bracket", "[", REG_EBRACK },
{ "unterminated comment", "(?#", REG_BADPAT },
{ "unterminated inline flags", "(?i", REG_BADPAT },
{ "short hex escape", "\\x", REG_OK },
{ "unterminated wide hex", "\\x{", REG_EBRACE },
{ "empty wide hex", "\\x{}", REG_OK }
};
size_t i;
for (i = 0; i < sizeof(cases) / sizeof(*cases); i++)
{
if (run_case(&cases[i], 0))
return 1;
if (run_case(&cases[i], 1))
return 1;
}
return 0;
}
+192
View File
@@ -0,0 +1,192 @@
/*
test-str-source.c - Sample program for using tre_reguexec()
This software is released under a BSD-style license.
See the file LICENSE for details and copyright.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* look for getopt in order to use a -o option for output. */
#if defined(HAVE_UNISTD_H)
#include <unistd.h>
#elif defined(HAVE_GETOPT_H)
#include <getopt.h>
#endif
#include "tre-internal.h"
static FILE *outf = NULL;
/* Context structure for the tre_str_source wrappers. */
typedef struct {
/* Our string. */
const char *str;
/* Current position in the string. */
size_t pos;
} str_handler_ctx;
/* The get_next_char() handler. Sets `c' to the value of the next character,
and increases `pos_add' by the number of bytes read. Returns 1 if the
string has ended, 0 if there are more characters. */
static int
str_handler_get_next(tre_char_t *c, unsigned int *pos_add, void *context)
{
str_handler_ctx *ctx = context;
unsigned char ch = ctx->str[ctx->pos];
#ifdef TRE_DEBUG
fprintf(outf, "str[%lu] = %d\n", (unsigned long)ctx->pos, ch);
#endif /* TRE_DEBUG */
*c = ch;
if (ch)
ctx->pos++;
*pos_add = 1;
return ch == '\0';
}
/* The rewind() handler. Resets the current position in the input string. */
static void
str_handler_rewind(size_t pos, void *context)
{
str_handler_ctx *ctx = context;
#ifdef TRE_DEBUG
fprintf(outf, "rewind to %lu\n", (unsigned long)pos);
#endif /* TRE_DEBUG */
ctx->pos = pos;
}
/* The compare() handler. Compares two substrings in the input and returns
0 if the substrings are equal, and a nonzero value if not. */
static int
str_handler_compare(size_t pos1, size_t pos2, size_t len, void *context)
{
str_handler_ctx *ctx = context;
#ifdef TRE_DEBUG
fprintf(outf, "comparing %lu-%lu and %lu-%lu\n",
(unsigned long)pos1, (unsigned long)pos1 + len,
(unsigned long)pos2, (unsigned long)pos2 + len);
#endif /* TRE_DEBUG */
return strncmp(ctx->str + pos1, ctx->str + pos2, len);
}
/* Creates a tre_str_source wrapper around the string `str'. Returns the
tre_str_source object or NULL if out of memory. */
static tre_str_source *
make_str_source(const char *str)
{
tre_str_source *s;
str_handler_ctx *ctx;
s = calloc(1, sizeof(*s));
if (!s)
return NULL;
ctx = malloc(sizeof(str_handler_ctx));
if (!ctx)
{
free(s);
return NULL;
}
ctx->str = str;
ctx->pos = 0;
s->context = ctx;
s->get_next_char = str_handler_get_next;
s->rewind = str_handler_rewind;
s->compare = str_handler_compare;
return s;
}
/* Frees the memory allocated for `s'. */
static void
free_str_source(tre_str_source *s)
{
free(s->context);
free(s);
}
/* Run one test with tre_reguexec. Returns 1 if the regex matches, 0 if
it doesn't, and -1 if an error occurs. */
static int
test_reguexec(const char *str, const char *regex)
{
regex_t preg;
tre_str_source *source;
regmatch_t pmatch[5];
int ret;
if ((source = make_str_source(str)) == NULL)
{
fprintf(stderr, "Out of memory\n");
ret = -1;
}
else
{
if (tre_regcomp(&preg, regex, REG_EXTENDED) != REG_OK)
{
fprintf(stderr, "Failed to compile /%s/\n", regex);
ret = -1;
}
else
{
if (tre_reguexec(&preg, source, elementsof(pmatch), pmatch, 0) == 0)
{
fprintf(outf, "Match: /%s/ matches \"%.*s\" in \"%s\"\n", regex,
(int)(pmatch[0].rm_eo - pmatch[0].rm_so),
str + pmatch[0].rm_so, str);
ret = 1;
}
else
{
fprintf(outf, "No match: /%s/ in \"%s\"\n", regex, str);
ret = 0;
}
tre_regfree(&preg);
}
free_str_source(source);
}
return ret;
}
int
main(int argc, char **argv)
{
int ret = 0;
outf = stdout;
#if defined(HAVE_UNISTD_H) || defined(HAVE_GETOPT_H)
int opt;
while ((opt = getopt(argc, argv, "o:")) != EOF)
{
switch (opt)
{
case 'o':
if ((outf = fopen(optarg, "w")) == NULL)
{
perror(optarg);
exit(1);
}
break;
default:
/* getopt() will have printed an error message already */
exit(1);
}
}
#endif
ret += test_reguexec("xfoofofoofoo", "(foo)\\1") != 1;
ret += test_reguexec("catcat", "(cat|dog)\\1") != 1;
ret += test_reguexec("catdog", "(cat|dog)\\1") != 0;
ret += test_reguexec("dogdog", "(cat|dog)\\1") != 1;
ret += test_reguexec("dogcat", "(cat|dog)\\1") != 0;
return ret;
}
+3 -16
View File
@@ -11,7 +11,7 @@ all: prepare_source
get_source:
$(call submake,$@)
prepare_source: get_source handle-werrors setup_environment
prepare_source: get_source setup_environment
clean:
$(call submake,$@)
@@ -25,7 +25,7 @@ pristine:
install:
$(call submake,$@)
setup_environment: install-rust handle-werrors
setup_environment: install-rust
clean_environment: uninstall-rust
@@ -74,17 +74,4 @@ ifeq ($(INSTALL_RUST_TOOLCHAIN),yes)
fi
endif
handle-werrors: get_source
ifeq ($(DISABLE_WERRORS),yes)
@echo "Disabling -Werror for all modules"
@for dir in $(SUBDIRS); do \
echo "Processing $$dir"; \
find $$dir/src -type f \
\( -name "Makefile" \
-o -name "*.mk" \
-o -name "CMakeLists.txt" \) \
-exec sed -i 's/-Werror//g' {} +; \
done
endif
.PHONY: all clean distclean install $(SUBDIRS) setup_environment clean_environment install-rust uninstall-rust handle-werrors
.PHONY: all clean distclean install $(SUBDIRS) setup_environment clean_environment install-rust uninstall-rust
+1 -1
View File
@@ -1,5 +1,5 @@
SRC_DIR = src
MODULE_VERSION = v8.7.80
MODULE_VERSION = v8.8.0
MODULE_REPO = https://github.com/redisbloom/redisbloom
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/redisbloom.so
+15 -2
View File
@@ -1,7 +1,20 @@
SRC_DIR = src
MODULE_VERSION = v8.5.90
MODULE_VERSION = v8.8.0
MODULE_REPO = https://github.com/redisearch/redisearch
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/search-community/redisearch.so
include ../common.mk
# Enable link-time optimization for RediSearch by default. Override with LTO=0.
LTO ?= 1
export LTO
# Use the committed C headers for Rust modules, rather than regenerating them
# from Rust source. Override with REDISEARCH_GENERATE_HEADERS=1.
REDISEARCH_GENERATE_HEADERS ?= 0
export REDISEARCH_GENERATE_HEADERS
# Set INLINE_LSE_ATOMICS=1 for perf improvement on common ARM CPUs (i.e. Graviton2/3/4); no effect on x86 or macOS.
# Default 0 keeps the binary runnable on pre-Armv8.1-a cores (Cortex-A72, Graviton1, RPi4) that would otherwise SIGILL at module load.
INLINE_LSE_ATOMICS ?= 0
export INLINE_LSE_ATOMICS
include ../common.mk
+1 -1
View File
@@ -1,5 +1,5 @@
SRC_DIR = src
MODULE_VERSION = v8.7.80
MODULE_VERSION = v8.8.0
MODULE_REPO = https://github.com/redisjson/redisjson
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/rejson.so
+1 -1
View File
@@ -1,5 +1,5 @@
SRC_DIR = src
MODULE_VERSION = v8.7.80
MODULE_VERSION = v8.8.0
MODULE_REPO = https://github.com/redistimeseries/redistimeseries
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/redistimeseries.so
+2 -2
View File
@@ -96,10 +96,10 @@ class TestCase:
self.error_details = None
self.test_key = f"test:{self.__class__.__name__.lower()}"
# Primary Redis instance
self.redis = redis.Redis(port=primary_port,db=9)
self.redis = redis.Redis(port=primary_port,protocol=2,db=9)
self.redis3 = redis.Redis(port=primary_port,protocol=3,db=9)
# Replica Redis instance
self.replica = redis.Redis(port=replica_port,db=9)
self.replica = redis.Redis(port=replica_port,protocol=2,db=9)
# Replication status
self.replication_setup = False
# Ports
@@ -0,0 +1,129 @@
from test import TestCase, generate_random_vector
import struct
import redis.exceptions
MAX_DIM = 65536
class DimensionMaxLimitVaddAtLimit(TestCase):
def getname(self):
return "[regression] VADD VALUES dim == MAX_DIM accepted"
def estimated_runtime(self):
return 0.5
def test(self):
dim = MAX_DIM
vec = generate_random_vector(dim)
result = self.redis.execute_command(
'VADD', self.test_key,
'VALUES', dim,
*[str(x) for x in vec],
f"{self.test_key}:item:maxdim")
assert result == 1, "VADD with dimension at the limit should succeed"
class DimensionMaxLimitVaddAboveLimit(TestCase):
def getname(self):
return "[regression] VADD VALUES dim > MAX_DIM rejected"
def estimated_runtime(self):
return 0.1
def test(self):
too_big_dim = MAX_DIM + 1
too_big_vec = generate_random_vector(16)
try:
self.redis.execute_command(
'VADD', self.test_key,
'VALUES', too_big_dim,
*[str(x) for x in too_big_vec],
f"{self.test_key}:item:toolarge")
assert False, "VADD with dimension above the limit should fail"
except redis.exceptions.ResponseError as e:
# parseVector returns NULL so caller uses the generic invalid spec error
assert "invalid vector specification" in str(e), (
f"Expected invalid vector specification error, got: {e}")
class DimensionMaxLimitVsimAtLimit(TestCase):
def getname(self):
return "[regression] VSIM VALUES dim == MAX_DIM accepted"
def estimated_runtime(self):
return 0.5
def test(self):
# Insert a vector at the maximum allowed dimension, then query at the same dimension.
dim = MAX_DIM
base_vec = generate_random_vector(dim)
result = self.redis.execute_command(
'VADD', self.test_key,
'VALUES', dim,
*[str(x) for x in base_vec],
f"{self.test_key}:item:1")
assert result == 1, "VADD with dimension at the limit should succeed"
query = generate_random_vector(dim)
res = self.redis.execute_command(
'VSIM', self.test_key,
'VALUES', dim,
*[str(x) for x in query],
'COUNT', 1)
assert isinstance(res, list), "VSIM with dimension at the limit should return a list"
class DimensionMaxLimitVsimAboveLimit(TestCase):
def getname(self):
return "[regression] VSIM VALUES dim > MAX_DIM rejected"
def estimated_runtime(self):
return 0.1
def test(self):
# Create a small index, then issue a VSIM with an over-limit dimension.
base_dim = 16
base_vec = generate_random_vector(base_dim)
result = self.redis.execute_command(
'VADD', self.test_key,
'VALUES', base_dim,
*[str(x) for x in base_vec],
f"{self.test_key}:item:1")
assert result == 1, "VADD with base_dim should succeed"
too_big_dim = MAX_DIM + 1
too_big_vec = generate_random_vector(16)
try:
self.redis.execute_command(
'VSIM', self.test_key,
'VALUES', too_big_dim,
*[str(x) for x in too_big_vec],
'COUNT', 1)
assert False, "VSIM with dimension above the limit should fail"
except redis.exceptions.ResponseError as e:
assert "invalid vector specification" in str(e), (
f"Expected invalid vector specification error in VSIM, got: {e}")
class DimensionMaxLimitHugeDimension(TestCase):
def getname(self):
return "[regression] VADD VALUES absurdly large dim rejected"
def estimated_runtime(self):
return 0.1
def test(self):
# Extremely large dimension close to LLONG_MAX should also be rejected safely.
huge_dim = 9223372036854775807 # LLONG_MAX from the original report
try:
self.redis.execute_command(
'VADD', self.test_key,
'VALUES', huge_dim,
'0') # Just a dummy value; parseVector should reject based on dimension alone
assert False, "VADD with absurdly large dimension should fail"
except redis.exceptions.ResponseError as e:
assert "invalid vector specification" in str(e), (
f"Expected invalid vector specification error for huge dim, got: {e}")
@@ -65,3 +65,33 @@ class DimensionValidation(TestCase):
assert False, "VSIM with wrong dimension should fail"
except redis.exceptions.ResponseError as e:
assert "Input dimension mismatch for projection" in str(e), f"Expected dimension mismatch error in VSIM, got: {e}"
class ReduceDimConstraintValidation(TestCase):
def getname(self):
return "[regression] VADD enforces reduce_dim <= dim"
def estimated_runtime(self):
return 0.1
def test(self):
import struct
dim = 16
reduce_dim = dim + 1 # Intentionally larger than dim
# Build a simple FP32 vector of the given dimension.
vec = [0.0] * dim
vec_bytes = struct.pack(f'{dim}f', *vec)
try:
self.redis.execute_command(
'VADD', self.test_key,
'REDUCE', reduce_dim,
'FP32', vec_bytes,
f'{self.test_key}:item:reducemismatch')
assert False, "VADD with reduce_dim > dim should fail"
except redis.exceptions.ResponseError as e:
# Same generic validation error path as other vector spec problems.
assert "invalid vector specification" in str(e), (
f"Expected invalid vector error, got: {e}")
+55 -6
View File
@@ -134,6 +134,9 @@ static uint64_t VectorSetTypeNextId = 0;
// Default num elements returned by VSIM.
#define VSET_DEFAULT_COUNT 10
// Maximum allowed vector dimension for input vectors and sets.
#define VSET_MAX_VECTOR_DIM (1<<16)
/* ========================== Internal data structure ====================== */
/* Our abstract data type needs a dual representation similar to Redis
@@ -408,6 +411,7 @@ float *parseVector(RedisModuleString **argv, int argc, int start_idx,
// Must be 4 bytes per component.
if (vec_raw_len % 4 || vec_raw_len < 4) return NULL;
*dim = vec_raw_len/4;
if (*dim > VSET_MAX_VECTOR_DIM) return NULL;
vec = RedisModule_Alloc(vec_raw_len);
if (!vec) return NULL;
@@ -417,7 +421,7 @@ float *parseVector(RedisModuleString **argv, int argc, int start_idx,
if (argc < start_idx + 2) return NULL; // Need at least the dimension.
long long vdim; // Vector dimension passed by the user.
if (RedisModule_StringToLongLong(argv[start_idx+1],&vdim)
!= REDISMODULE_OK || vdim < 1) return NULL;
!= REDISMODULE_OK || vdim < 1 || vdim > VSET_MAX_VECTOR_DIM) return NULL;
// Check that all the arguments are available.
if (argc < start_idx + 2 + vdim) return NULL;
@@ -441,6 +445,12 @@ float *parseVector(RedisModuleString **argv, int argc, int start_idx,
return NULL; // Unknown format.
}
// reduce_dim must be <= dim
if (reduce_dim && *reduce_dim && *reduce_dim > *dim) {
if (vec) RedisModule_Free(vec);
return NULL;
}
if (consumed_args) *consumed_args = consumed;
return vec;
}
@@ -1966,6 +1976,15 @@ void *VectorSetRdbLoad(RedisModuleIO *rdb, int encver) {
uint32_t quant_type = hnsw_config & 0xff;
uint32_t hnsw_m = (hnsw_config >> 8) & 0xffff;
/* Validate dimension loaded from RDB to enforce invariants and
* avoid absurd allocations or inconsistent state. */
if (dim == 0 || dim > VSET_MAX_VECTOR_DIM) {
RedisModule_LogIOError(rdb, "warning",
"Invalid vector dimension in RDB: dim=%u (max allowed %u)",
(unsigned)dim, (unsigned)VSET_MAX_VECTOR_DIM);
return NULL;
}
/* Check that the quantization type is correct. Otherwise
* return ASAP signaling the error. */
if (quant_type != HNSW_QUANT_NONE &&
@@ -1987,14 +2006,44 @@ void *VectorSetRdbLoad(RedisModuleIO *rdb, int encver) {
uint32_t input_dim = RedisModule_LoadUnsigned(rdb);
if (RedisModule_IsIOError(rdb)) goto ioerr;
uint32_t output_dim = dim;
size_t matrix_size = sizeof(float) * input_dim * output_dim;
/* Sanity check projection dimensions. */
if (input_dim == 0 || output_dim == 0 || input_dim > VSET_MAX_VECTOR_DIM || output_dim > input_dim) {
RedisModule_LogIOError(rdb, "warning",
"Invalid projection matrix dimensions: input_dim=%u, output_dim=%u (max allowed %u)",
(unsigned)input_dim, (unsigned)output_dim,
(unsigned)VSET_MAX_VECTOR_DIM);
goto ioerr;
}
/* Check for overflow in matrix_size = sizeof(float) * input_dim * output_dim. */
#if SIZE_MAX == UINT32_MAX
uint64_t product = (uint64_t) output_dim * (uint64_t) input_dim * sizeof(float);
if (product > SIZE_MAX) {
RedisModule_LogIOError(rdb, "warning",
"Projection matrix size overflow (output_dim too large): input_dim=%u, output_dim=%u",
(unsigned)input_dim, (unsigned)output_dim);
goto ioerr;
}
#endif
size_t matrix_size = sizeof(float) * (size_t)input_dim * (size_t)output_dim;
/* Load projection matrix as a binary blob and validate length. */
size_t blob_len = 0;
char *matrix_blob = RedisModule_LoadStringBuffer(rdb, &blob_len);
if (matrix_blob == NULL) goto ioerr;
if (blob_len != matrix_size) {
RedisModule_LogIOError(rdb, "warning",
"Mismatching projection matrix length: expected=%zu, got=%zu",
matrix_size, blob_len);
RedisModule_Free(matrix_blob);
goto ioerr;
}
vset->proj_matrix = RedisModule_Alloc(matrix_size);
vset->proj_input_size = input_dim;
// Load projection matrix as a binary blob
char *matrix_blob = RedisModule_LoadStringBuffer(rdb, NULL);
if (matrix_blob == NULL) goto ioerr;
memcpy(vset->proj_matrix, matrix_blob, matrix_size);
RedisModule_Free(matrix_blob);
}
+50 -1
View File
@@ -1971,6 +1971,17 @@ slowlog-log-slower-than 10000
# You can reclaim memory used by the slow log with SLOWLOG RESET.
slowlog-max-len 128
# When a command is written to the slowlog we check how many arguments it has
# and if it has more than slowlog-entry-max-argc we trim the excess ones. The
# last of the non-trimmed arguments is overwritten with an info string about
# how many args were trimmed. That's why slowlog-entry-max-argc has minimum
# value of 2, so we can always preserve the command name.
# Moreover, each individual argument string is also trimmed depending on
# slowlog-entry-max-string-len. Default values:
#
# slowlog-entry-max-argc 32
# slowlog-entry-max-string-len 128
################################ LATENCY MONITOR ##############################
# The Redis latency monitoring subsystem samples different operations
@@ -2033,13 +2044,20 @@ latency-monitor-threshold 0
# e Evicted events (events generated when a key is evicted for maxmemory)
# n New key events (Note: not included in the 'A' class)
# t Stream commands
# a Array commands
# d Module key type events
# m Key-miss events (Note: It is not included in the 'A' class)
# o Overwritten events generated every time a key is overwritten.
# (Note: not included in the 'A' class)
# c Type-changed events generated every time a key's type changes
# (Note: not included in the 'A' class)
# A Alias for g$lshzxetd, so that the "AKE" string means all the events
# S Subkeyspace events, published with __subkeyspace@<db>__:<key> prefix.
# T Subkeyevent events, published with __subkeyevent@<db>__:<event> prefix.
# I Subkeyspaceitem events, published per subkey with
# __subkeyspaceitem@<db>__:<key>\n<subkey> prefix.
# V Subkeyspaceevent events, published with
# __subkeyspaceevent@<db>__:<event>|<key> prefix.
# A Alias for g$lshzxetad, so that the "AKE" string means all the events
# except key-miss, new key, overwritten and type-changed.
#
# The "notify-keyspace-events" takes as argument a string that is composed
@@ -2169,6 +2187,37 @@ stream-node-max-entries 100
# stream-idmp-duration 100
# stream-idmp-maxsize 100
# Arrays use a sliced directory structure for O(1) access. The slice size
# controls the granularity of memory allocation - each slice covers a range
# of indices. Must be a power of two between 256 and 65536.
#
# Smaller slices (1024-2048): Better for sparse data with large gaps between
# indices, or many small arrays. Uses less memory per slice but more directory
# entries.
#
# Larger slices (8192-16384): Better for dense/contiguous data. Fewer directory
# entries but may waste memory if data is sparse within slices.
#
# Default 4096 works well for mixed workloads. If you change this setting via
# CONFIG SET, existing arrays retain their original slice size.
#
# IMPORTANT CONSIDERATION: Redis arrays, for slices with very few elements, are
# able to use a sparse representation, where the slice is not really
# materialized into an actual contiguous allocation. See the next configuration
# parameters for more information.
array-slice-size 4096
# Arrays start with sparse slices (sorted key-value pairs) for memory efficiency
# when elements are scattered. When a sparse slice exceeds array-sparse-kmax
# entries, it promotes to a dense slice (direct array). When a dense slice's
# element count drops below array-sparse-kmin and demotion would save memory,
# it demotes back to sparse. Set kmax to 0 to disable sparse encoding entirely.
# Set kmin to 0 if you never want dense slices to be demoted to sparse (useful
# when in your work load arrays reach an almost empty state to be filled again
# and so forth).
array-sparse-kmax 10
array-sparse-kmin 5
# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in
# order to help rehashing the main Redis hash table (the one mapping top-level
# keys to values). The hash table implementation Redis uses (see dict.c)
+5
View File
@@ -1,4 +1,9 @@
#!/bin/sh
# Raise open files limit (macOS default 256 is too low for tests).
OPEN_FILE_LIMIT=$(ulimit -n 2>/dev/null)
if [ -n "$OPEN_FILE_LIMIT" ] && [ "$OPEN_FILE_LIMIT" != "unlimited" ] && [ "$OPEN_FILE_LIMIT" -lt 1024 ]; then
ulimit -n 1024 2>/dev/null || true
fi
TCL_VERSIONS="8.5 8.6 8.7 9.0"
TCLSH=""
+5
View File
@@ -1,4 +1,9 @@
#!/bin/sh
# Raise open files limit (macOS default 256 is too low for tests).
OPEN_FILE_LIMIT=$(ulimit -n 2>/dev/null)
if [ -n "$OPEN_FILE_LIMIT" ] && [ "$OPEN_FILE_LIMIT" != "unlimited" ] && [ "$OPEN_FILE_LIMIT" -lt 1024 ]; then
ulimit -n 1024 2>/dev/null || true
fi
TCL_VERSIONS="8.5 8.6 8.7 9.0"
TCLSH=""
+5
View File
@@ -1,4 +1,9 @@
#!/bin/sh
# Raise open files limit (macOS default 256 is too low for tests).
OPEN_FILE_LIMIT=$(ulimit -n 2>/dev/null)
if [ -n "$OPEN_FILE_LIMIT" ] && [ "$OPEN_FILE_LIMIT" != "unlimited" ] && [ "$OPEN_FILE_LIMIT" -lt 1024 ]; then
ulimit -n 1024 2>/dev/null || true
fi
TCL_VERSIONS="8.5 8.6 8.7 9.0"
TCLSH=""
[ -z "$MAKE" ] && MAKE=make
+5
View File
@@ -1,4 +1,9 @@
#!/bin/sh
# Raise open files limit (macOS default 256 is too low for tests).
OPEN_FILE_LIMIT=$(ulimit -n 2>/dev/null)
if [ -n "$OPEN_FILE_LIMIT" ] && [ "$OPEN_FILE_LIMIT" != "unlimited" ] && [ "$OPEN_FILE_LIMIT" -lt 1024 ]; then
ulimit -n 1024 2>/dev/null || true
fi
TCL_VERSIONS="8.5 8.6 8.7 9.0"
TCLSH=""
+10 -8
View File
@@ -25,17 +25,19 @@ CLANG := $(findstring clang,$(shell sh -c '$(CC) --version | head -1'))
# some automatic defaults are added to it. To specify optimization flags
# explicitly without any defaults added, pass the OPT variable instead.
OPTIMIZATION?=-O3
ENABLE_LTO?=
ifeq ($(OPTIMIZATION),-O3)
ifeq (clang,$(CLANG))
OPTIMIZATION+=-flto
ENABLE_LTO=-flto
else
OPTIMIZATION+=-flto=auto
ENABLE_LTO=-flto=auto -ffat-lto-objects
endif
OPTIMIZATION+=$(ENABLE_LTO)
endif
ifneq ($(OPTIMIZATION),-O0)
OPTIMIZATION+=-fno-omit-frame-pointer
endif
DEPENDENCY_TARGETS=hiredis linenoise lua hdr_histogram fpconv fast_float xxhash
DEPENDENCY_TARGETS=hiredis linenoise lua hdr_histogram fpconv xxhash tre
NODEPS:=clean distclean
# Default settings
@@ -149,7 +151,7 @@ endif
FINAL_CFLAGS=$(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(REDIS_CFLAGS)
FINAL_LDFLAGS=$(LDFLAGS) $(OPT) $(REDIS_LDFLAGS) $(DEBUG)
FINAL_LIBS=-lm -lstdc++
FINAL_LIBS=-lm
DEBUG=-g -ggdb
# Linux ARM32 needs -latomic at linking time
@@ -257,7 +259,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 -I../deps/xxhash
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src -I../deps/hdr_histogram -I../deps/fpconv -I../deps/xxhash
# Determine systemd support and/or build preference (defaulting to auto-detection)
BUILD_WITH_SYSTEMD=no
@@ -382,7 +384,7 @@ endif
REDIS_SERVER_NAME=redis-server$(PROG_SUFFIX)
REDIS_SENTINEL_NAME=redis-sentinel$(PROG_SUFFIX)
REDIS_SERVER_OBJ=threads_mngr.o memory_prefetch.o adlist.o quicklist.o ae.o anet.o dict.o ebuckets.o eventnotifier.o iothread.o mstr.o entry.o kvstore.o fwtree.o estore.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o cluster_asm.o cluster_legacy.o cluster_slot_stats.o crc16.o endianconv.o slowlog.o eval.o bio.o rio.o rand.o memtest.o syscheck.o crcspeed.o crccombine.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o lolwut8.o acl.o tracking.o socket.o tls.o sha256.o timeout.o setcpuaffinity.o monotonic.o mt19937-64.o resp_parser.o call_reply.o script_lua.o script.o functions.o function_lua.o commands.o strl.o connection.o unix.o logreqres.o keymeta.o chk.o hotkeys.o gcra.o
REDIS_SERVER_OBJ=threads_mngr.o memory_prefetch.o adlist.o quicklist.o ae.o anet.o dict.o ebuckets.o eventnotifier.o iothread.o mstr.o entry.o kvstore.o fwtree.o estore.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o t_array.o sparsearray.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o cluster_asm.o cluster_legacy.o cluster_slot_stats.o crc16.o endianconv.o slowlog.o eval.o bio.o rio.o rand.o memtest.o syscheck.o crcspeed.o crccombine.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o lolwut8.o acl.o tracking.o socket.o tls.o sha256.o timeout.o setcpuaffinity.o monotonic.o mt19937-64.o resp_parser.o call_reply.o script_lua.o script.o functions.o function_lua.o commands.o strl.o connection.o unix.o logreqres.o keymeta.o chk.o hotkeys.o gcra.o vector.o fast_float_strtod.o
REDIS_CLI_NAME=redis-cli$(PROG_SUFFIX)
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o ae.o redisassert.o crcspeed.o crccombine.o crc64.o siphash.o crc16.o monotonic.o cli_common.o mt19937-64.o strl.o cli_commands.o
REDIS_BENCHMARK_NAME=redis-benchmark$(PROG_SUFFIX)
@@ -423,7 +425,7 @@ persist-settings: distclean
echo REDIS_LDFLAGS=$(REDIS_LDFLAGS) >> .make-settings
echo PREV_FINAL_CFLAGS=$(FINAL_CFLAGS) >> .make-settings
echo PREV_FINAL_LDFLAGS=$(FINAL_LDFLAGS) >> .make-settings
-(cd ../deps && $(MAKE) $(DEPENDENCY_TARGETS))
-(cd ../deps && $(MAKE) $(DEPENDENCY_TARGETS) ENABLE_LTO="$(ENABLE_LTO)")
.PHONY: persist-settings
@@ -442,7 +444,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 ../deps/xxhash/libxxhash.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/xxhash/libxxhash.a ../deps/tre/libtre.a $(FINAL_LIBS)
# redis-sentinel
$(REDIS_SENTINEL_NAME): $(REDIS_SERVER_NAME)
+4
View File
@@ -57,6 +57,7 @@ struct ACLCategoryItem {
{"list", ACL_CATEGORY_LIST},
{"hash", ACL_CATEGORY_HASH},
{"string", ACL_CATEGORY_STRING},
{"array", ACL_CATEGORY_ARRAY},
{"bitmap", ACL_CATEGORY_BITMAP},
{"hyperloglog", ACL_CATEGORY_HYPERLOGLOG},
{"geo", ACL_CATEGORY_GEO},
@@ -70,6 +71,9 @@ struct ACLCategoryItem {
{"connection", ACL_CATEGORY_CONNECTION},
{"transaction", ACL_CATEGORY_TRANSACTION},
{"scripting", ACL_CATEGORY_SCRIPTING},
#ifdef ENABLE_GCRA
{"ratelimit", ACL_CATEGORY_RATE_LIMIT},
#endif
{NULL,0} /* Terminator. */
};
+215 -7
View File
@@ -2197,6 +2197,35 @@ int rioWriteStreamPendingEntry(rio *r, robj *key, const char *groupname, size_t
return 1;
}
/* Helper for rewriteStreamObject(): emit a single XNACK FORCE command that
* reconstructs one or more NACKed (unowned) PEL entries sharing the same
* delivery_count. `ids` points to an array of `count` streamIDs (at most
* AOF_REWRITE_ITEMS_PER_CMD). Returns 0 on error, 1 on success. */
int rioWriteStreamNackedEntries(rio *r, robj *key, const char *groupname,
size_t groupname_len, streamID *ids,
int count, uint64_t delivery_count) {
serverAssert(count > 0 && count <= AOF_REWRITE_ITEMS_PER_CMD);
/* XNACK <key> <group> FAIL IDS <n> <id..> RETRYCOUNT <cnt> FORCE
* 6 fixed tokens before IDs + count IDs + 3 fixed tokens after. */
if (rioWriteBulkCount(r,'*',6+count+3) == 0) return 0;
if (rioWriteBulkString(r,"XNACK",5) == 0) return 0;
if (rioWriteBulkObject(r,key) == 0) return 0;
if (rioWriteBulkString(r,groupname,groupname_len) == 0) return 0;
if (rioWriteBulkString(r,"FAIL",4) == 0) return 0;
if (rioWriteBulkString(r,"IDS",3) == 0) return 0;
if (rioWriteBulkLongLong(r,count) == 0) return 0;
for (int i = 0; i < count; i++) {
if (rioWriteBulkStreamID(r,&ids[i]) == 0) return 0;
}
if (rioWriteBulkString(r,"RETRYCOUNT",10) == 0) return 0;
if (rioWriteBulkLongLong(r,delivery_count) == 0) return 0;
if (rioWriteBulkString(r,"FORCE",5) == 0) return 0;
return 1;
}
/* Helper for rewriteStreamObject(): emit the XGROUP CREATECONSUMER is
* needed in order to create consumers that do not have any pending entries.
* All this in the context of the specified key and group. */
@@ -2354,6 +2383,43 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) {
raxStop(&ri_pel);
}
raxStop(&ri_cons);
/* Emit XNACK FORCE for NACKed (unowned) entries from the
* NACK zone of the PEL time-ordered list
* (pel_time_head..pel_nack_tail). Consecutive entries with
* the same delivery_count are batched into a single command.
*
* nack_stop is the first node outside the NACK zone (or NULL
* when the zone extends to the end of the PEL). When
* pel_nack_tail is NULL (no NACKed entries) the guard below
* skips the whole block. */
streamNACK *nack_end = group->pel_nack_tail;
if (nack_end != NULL) {
streamID batch_ids[AOF_REWRITE_ITEMS_PER_CMD];
streamNACK *nack_stop = nack_end->pel_next;
streamNACK *nack = group->pel_time_head;
int batch_count = 0;
uint64_t batch_dc = 0;
while (nack && nack != nack_stop) {
if (batch_count == 0) batch_dc = nack->delivery_count;
batch_ids[batch_count++] = nack->id;
streamNACK *next = nack->pel_next;
if (batch_count >= AOF_REWRITE_ITEMS_PER_CMD ||
!next || next == nack_stop ||
next->delivery_count != batch_dc)
{
if (rioWriteStreamNackedEntries(r,key,(char*)ri.key,
ri.key_len,batch_ids,
batch_count,batch_dc) == 0)
{
raxStop(&ri);
return 0;
}
batch_count = 0;
}
nack = next;
}
}
}
raxStop(&ri);
}
@@ -2401,6 +2467,20 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) {
return 1;
}
#ifdef ENABLE_GCRA
int rewriteGCRAObject(rio *r, robj *key, robj *o) {
long long val;
getLongLongFromGCRAObject(o, &val);
/* GCRASETVALUE <key> <tat> */
if (rioWriteBulkCount(r,'*',3) == 0) return 0;
if (rioWriteBulkString(r,"GCRASETVALUE",12) == 0) return 0;
if (rioWriteBulkObject(r,key) == 0) return 0;
if (rioWriteBulkLongLong(r,val) == 0) return 0;
return 1;
}
#endif
/* Call the module type callback in order to rewrite a data type
* that is exported by a module and is not handled by Redis itself.
* The function returns 0 on error, 1 on success. */
@@ -2437,6 +2517,116 @@ werr:
return 0;
}
/* Write unsigned 64-bit integer as bulk string.
* Unlike rioWriteBulkLongLong which uses signed representation,
* this correctly handles values >= 2^63 (e.g., array indices). */
static int rioWriteBulkUnsignedLongLong(rio *r, uint64_t value) {
char buf[24];
int len = ull2string(buf, sizeof(buf), value);
return rioWriteBulkString(r, buf, len);
}
/* Helper to emit a single array element for AOF rewrite.
* Returns 0 on error, 1 on success. Updates count and items. */
static int aofEmitArrayElement(rio *r, robj *key, uint64_t idx, void *v,
long long *count, long long *items) {
if (*count == 0) {
int cmd_items = (*items > AOF_REWRITE_ITEMS_PER_CMD/2) ?
AOF_REWRITE_ITEMS_PER_CMD/2 : *items; /* pairs of idx+val */
if (!rioWriteBulkCount(r,'*',2+cmd_items*2) ||
!rioWriteBulkString(r,"ARMSET",6) ||
!rioWriteBulkObject(r,key))
{
return 0;
}
}
/* Write index (unsigned to handle indices >= 2^63) */
if (!rioWriteBulkUnsignedLongLong(r, idx)) return 0;
/* Write value - inline types use scratch space, arString aliases directly. */
char buf[AR_INLINE_BUFSIZE];
size_t len;
const char *data = arDecode(v, buf, sizeof(buf), &len);
if (!rioWriteBulkString(r, data, len)) return 0;
if (++(*count) == AOF_REWRITE_ITEMS_PER_CMD/2) *count = 0;
(*items)--;
return 1;
}
/* Helper to emit all elements from a slice for AOF rewrite. */
static int aofEmitSliceElements(rio *r, robj *key, arSlice *s, uint64_t slice_id,
uint32_t slice_size, long long *count, long long *items) {
if (s->encoding == AR_SLICE_DENSE) {
for (uint32_t i = 0; i < s->layout.dense.winsize; i++) {
void *v = s->layout.dense.items[i];
if (arIsEmpty(v)) continue;
uint64_t idx = arMakeIdx(slice_id, s->layout.dense.offset + i, slice_size);
if (!aofEmitArrayElement(r, key, idx, v, count, items)) return 0;
}
} else {
/* Sparse slice */
uint16_t *offsets = s->layout.sparse.offsets;
void **values = s->layout.sparse.values;
for (uint32_t i = 0; i < s->count; i++) {
uint64_t idx = arMakeIdx(slice_id, offsets[i], slice_size);
if (!aofEmitArrayElement(r, key, idx, values[i], count, items)) return 0;
}
}
return 1;
}
/* Emit the commands needed to rebuild an array object.
* The function returns 0 on error, 1 on success. */
int rewriteArrayObject(rio *r, robj *key, robj *o) {
redisArray *ar = o->ptr;
long long count = 0, items = ar->count;
if (items == 0) return 1;
/* Iterate through all slices, handling both flat directory mode and
* superdir mode. This mirrors the iteration logic in rdb.c. */
if (ar->superdir) {
/* Superdir mode: iterate through blocks */
for (uint32_t bi = 0; bi < ar->sdir_len; bi++) {
arSDirEntry *e = ar->superdir + bi;
uint64_t block_base = e->block_id * AR_SUPER_BLOCK_SLOTS;
for (uint32_t si = 0; si < AR_SUPER_BLOCK_SLOTS; si++) {
arSlice *s = e->slots[si];
if (!s) continue;
uint64_t slice_id = block_base + si;
if (!aofEmitSliceElements(r, key, s, slice_id, ar->slice_size,
&count, &items)) return 0;
}
}
} else {
/* Flat directory mode */
for (uint64_t slice_id = 0; slice_id <= ar->dir_highest_used && slice_id < ar->dir_alloc; slice_id++) {
arSlice *s = ar->dir[slice_id];
if (!s) continue;
if (!aofEmitSliceElements(r, key, s, slice_id, ar->slice_size,
&count, &items)) return 0;
}
}
/* If insert_idx is set, emit ARSEEK command to restore it.
* When insert_idx == UINT64_MAX-1, we emit ARSEEK UINT64_MAX which
* correctly sets insert_idx back to UINT64_MAX-1 (terminal state). */
if (ar->insert_idx != AR_INSERT_IDX_NONE) {
/* ARSEEK key insert_idx+1 (ARSEEK sets position for next insert) */
if (!rioWriteBulkCount(r,'*',3) ||
!rioWriteBulkString(r,"ARSEEK",6) ||
!rioWriteBulkObject(r,key) ||
!rioWriteBulkUnsignedLongLong(r, ar->insert_idx + 1))
{
return 0;
}
}
return 1;
}
int rewriteObject(rio *r, robj *key, robj *o, int dbid, long long expiretime) {
/* Save the key and associated value */
if (o->type == OBJ_STRING) {
@@ -2456,6 +2646,12 @@ int rewriteObject(rio *r, robj *key, robj *o, int dbid, long long expiretime) {
if (rewriteHashObject(r,key,o) == 0) return C_ERR;
} else if (o->type == OBJ_STREAM) {
if (rewriteStreamObject(r,key,o) == 0) return C_ERR;
#ifdef ENABLE_GCRA
} else if (o->type == OBJ_GCRA) {
if (rewriteGCRAObject(r,key,o) == 0) return C_ERR;
#endif
} else if (o->type == OBJ_ARRAY) {
if (rewriteArrayObject(r,key,o) == 0) return C_ERR;
} else if (o->type == OBJ_MODULE) {
if (rewriteModuleObject(r,key,o,dbid) == 0) return C_ERR;
} else {
@@ -2504,10 +2700,20 @@ int rewriteAppendOnlyFileRio(rio *aof) {
if (rioWriteBulkLongLong(aof,j) == 0) goto werr;
kvstoreIteratorInit(&kvs_it, db->keys);
int last_slot = -1;
/* Iterate this DB writing every entry */
while((de = kvstoreIteratorNext(&kvs_it)) != NULL) {
long long expiretime;
size_t aof_bytes_before_key = aof->processed_bytes;
int curr_slot = kvstoreIteratorGetCurrentDictIndex(&kvs_it);
/* In cluster mode, dismiss bucket arrays of the previous slot
* which won't be accessed again, to avoid CoW. */
if (server.cluster_enabled && curr_slot != last_slot) {
if (server.in_fork_child && last_slot != -1)
dismissDictBucketsMemory(kvstoreGetDict(db->keys, last_slot));
last_slot = curr_slot;
}
/* Get the value object (of type kvobj) */
kvobj *o = dictGetKV(de);
@@ -2516,12 +2722,9 @@ int rewriteAppendOnlyFileRio(rio *aof) {
expiretime = kvobjGetExpire(o);
/* Skip keys that are being trimmed */
if (server.cluster_enabled) {
int curr_slot = kvstoreIteratorGetCurrentDictIndex(&kvs_it);
if (isSlotInTrimJob(curr_slot)) {
skipped++;
continue;
}
if (server.cluster_enabled && isSlotInTrimJob(curr_slot)) {
skipped++;
continue;
}
/* Set on stack string object for key */
@@ -2534,7 +2737,8 @@ int rewriteAppendOnlyFileRio(rio *aof) {
* OS and possibly avoid or decrease COW. We give the dismiss
* mechanism a hint about an estimated size of the object we stored. */
size_t dump_size = aof->processed_bytes - aof_bytes_before_key;
if (server.in_fork_child) dismissObject(o, dump_size);
if (server.in_fork_child && dump_size > server.page_size/2)
dismissObject(o, dump_size);
/* Update info every 1 second (approximately).
* in order to avoid calling mstime() on each iteration, we will
@@ -2552,6 +2756,10 @@ int rewriteAppendOnlyFileRio(rio *aof) {
debugDelay(server.rdb_key_save_delay);
}
kvstoreIteratorReset(&kvs_it);
/* Dismiss bucket arrays of kvstore in standalone mode. */
if (server.in_fork_child && !server.cluster_enabled)
dismissKvstoreBucketsMemory(db->keys);
}
serverLog(LL_NOTICE, "AOF rewrite done, %ld keys saved, %llu keys skipped.", key_count, skipped);
return C_OK;
+19
View File
@@ -183,4 +183,23 @@
#error "Unable to determine atomic operations for your platform"
#endif
/* atomicIncrGetSingleWriter(var, delta, newvalue_var)
*
* Adds `delta` to `var` and writes the resulting value to `newvalue_var`.
* Same end result as atomicIncrGet() but implemented as load+add+store instead
* of an atomic read-modify-write. This avoids the `lock` prefix on x86
* (~20-40 cycles vs ~2-3 for plain load+store).
*
* SAFETY: the caller MUST guarantee that no other thread ever writes to `var`
* (no atomicIncr, no atomicSet, no other call to this macro from a different
* thread). Concurrent writers cause silent lost updates. Readers on other
* threads using atomicGet are fine: they will observe either the pre or
* post update value. */
#define atomicIncrGetSingleWriter(var, delta, newvalue_var) do { \
atomicGet((var), (newvalue_var)); \
(newvalue_var) += (delta); \
atomicSet((var), (newvalue_var)); \
} while(0)
#endif /* __ATOMIC_VAR_H */
+7 -1
View File
@@ -699,7 +699,13 @@ static void unblockClientOnKey(client *c, robj *key) {
client *old_client = server.current_client;
server.current_client = c;
enterExecutionUnit(1, 0);
processCommandAndResetClient(c);
if (processCommandAndResetClient(c) == C_ERR) {
/* Client was freed during command processing, exit immediately */
exitExecutionUnit();
server.current_client = old_client;
return;
}
if (!(c->flags & CLIENT_BLOCKED)) {
if (c->flags & CLIENT_MODULE) {
moduleCallCommandUnblockedHandler(c);
+10 -3
View File
@@ -324,11 +324,18 @@ void parseRedisUri(const char *uri, const char* tool_name, cliConnInfo *connInfo
/* Extract user info. */
if ((userinfo = strchr(curr,'@'))) {
if ((username = strchr(curr, ':')) && username < userinfo) {
connInfo->user = percentDecode(curr, username - curr);
/* Free any value previously set via --user / -a (later
* parameters override earlier ones) and use NULL for an
* explicitly empty component, so cliAuth() falls back to the
* legacy single-argument AUTH (empty username) or skips AUTH
* entirely (empty password) instead of sending an empty ACL
* component, which the server rejects. */
sdsfree(connInfo->user);
connInfo->user = (username > curr) ? percentDecode(curr, username - curr) : NULL;
curr = username + 1;
}
connInfo->auth = percentDecode(curr, userinfo - curr);
sdsfree(connInfo->auth);
connInfo->auth = (userinfo > curr) ? percentDecode(curr, userinfo - curr) : NULL;
curr = userinfo + 1;
}
if (curr == end) return;
+22 -7
View File
@@ -321,6 +321,7 @@ void restoreCommand(client *c) {
objectSetLRUOrLFU(kv, lfu_freq, lru_idle, lru_clock, 1000);
keyModified(c,c->db,key,NULL,1);
notifyKeyspaceEvent(NOTIFY_GENERIC,"restore",key,c->db->id);
KSN_INVALIDATE_KVOBJ(kv);
/* If we deleted a key that means REPLACE parameter was passed and the
* destination key existed. */
@@ -803,7 +804,12 @@ int verifyClusterNodeId(const char *name, int length) {
}
int isValidAuxChar(int c) {
return isalnum(c) || (strchr("!#$%&()*+:;<>?@[]^{|}~", c) == NULL);
/* Reject control characters (0x00-0x1F and 0x7F). */
if (iscntrl(c)) {
return 0;
}
/* Reject forbidden characters including nodes.conf delimiters and special parsing characters */
return isalnum(c) || (strchr("!#$%&()*+:;<>?@[]^{|}~,= \"'\\", c) == NULL);
}
int isValidAuxString(char *s, unsigned int length) {
@@ -1740,7 +1746,7 @@ unsigned int clusterDelKeysInSlot(unsigned int hashslot, int by_command) {
* just moved to another node. The modules needs to know that these
* keys are no longer available locally, so just send the keyspace
* notification to the modules, but not to clients. */
moduleNotifyKeyspaceEvent(NOTIFY_GENERIC, "del", key, server.db[0].id);
moduleNotifyKeyspaceEvent(NOTIFY_GENERIC, "del", key, server.db[0].id, NULL, 0);
}
exitExecutionUnit();
postExecutionUnitOperations();
@@ -2101,17 +2107,26 @@ int clusterCanAccessKeysInSlot(int slot) {
return 0;
}
/* Return the slot ranges that belong to the current node or its master. */
/* Return the slot ranges that belong to the current node or its master.
* In non-cluster mode, returns the full slot range (0-16383). */
slotRangeArray *clusterGetLocalSlotRanges(void) {
slotRangeArray *slots = NULL;
if (!server.cluster_enabled) {
slots = slotRangeArrayCreate(1);
slotRangeArray *slots = slotRangeArrayCreate(1);
slotRangeArraySet(slots, 0, 0, CLUSTER_SLOTS - 1);
return slots;
}
clusterNode *master = clusterNodeGetMaster(getMyClusterNode());
return clusterGetNodeSlotRanges(getMyClusterNode());
}
/* Returns the slot ranges owned by the given node.
* If the node is a replica, the master's slot ranges are returned.
* Returns an empty array if the node has no slots. */
slotRangeArray *clusterGetNodeSlotRanges(clusterNode *node) {
slotRangeArray *slots = NULL;
serverAssert(server.cluster_enabled && node != NULL);
clusterNode *master = clusterNodeGetMaster(node);
if (master) {
for (int i = 0; i < CLUSTER_SLOTS; i++) {
if (clusterNodeCoversSlot(master, i))
+1
View File
@@ -154,6 +154,7 @@ int getSlotOrReply(client *c, robj *o);
int clusterIsMySlot(int slot);
int clusterCanAccessKeysInSlot(int slot);
struct slotRangeArray *clusterGetLocalSlotRanges(void);
struct slotRangeArray *clusterGetNodeSlotRanges(clusterNode *node);
/* functions with shared implementations */
clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, int argc, int *hashslot,
+21 -7
View File
@@ -589,7 +589,7 @@ size_t asmGetImportInputBufferSize(void) {
return 0;
}
size_t asmGetMigrateOutputBufferSize(void) {
size_t asmGetMigrateOutputMemoryUsage(void) {
if (!asmManager || listLength(asmManager->tasks) == 0) return 0;
asmTask *task = listNodeValue(listFirst(asmManager->tasks));
@@ -1057,13 +1057,27 @@ void clusterMigrationCommand(client *c) {
}
}
/* Returns the address of the node in the format "ip:port". */
static const char *getNodeAddressStr(const char *node_id, int len) {
serverAssert(node_id != NULL);
static char buf[NET_HOST_PORT_STR_LEN];
clusterNode *n = clusterLookupNode(node_id, len);
char *ip = n ? clusterNodeIp(n) : "?";
int port = n ? (server.tls_replication ? clusterNodeTlsPort(n) :
clusterNodeTcpPort(n)) : 0;
formatAddr(buf, sizeof(buf), ip, port);
return buf;
}
/* Log a human-readable message for ASM task lifecycle events. */
void asmLogTaskEvent(asmTask *task, int event) {
sds str = slotRangeArrayToString(task->slots);
switch (event) {
case ASM_EVENT_IMPORT_STARTED:
serverLog(LL_NOTICE, "Import task %s started for slots: %s", task->id, str);
serverLog(LL_NOTICE, "Import task %s started for slots: %s, source address: %s",
task->id, str, getNodeAddressStr(task->source, CLUSTER_NAMELEN));
break;
case ASM_EVENT_IMPORT_FAILED:
serverLog(LL_NOTICE, "Import task %s failed for slots: %s", task->id, str);
@@ -1076,8 +1090,8 @@ void asmLogTaskEvent(asmTask *task, int event) {
task->id, str, getKeyCountInSlotRangeArray(task->slots));
break;
case ASM_EVENT_MIGRATE_STARTED:
serverLog(LL_NOTICE, "Migrate task %s started for slots: %s (number of keys at start: %llu)",
task->id, str, getKeyCountInSlotRangeArray(task->slots));
serverLog(LL_NOTICE, "Migrate task %s started for slots: %s, destination address: %s, (number of keys at start: %llu)",
task->id, str, getNodeAddressStr(task->dest, CLUSTER_NAMELEN), getKeyCountInSlotRangeArray(task->slots));
break;
case ASM_EVENT_MIGRATE_FAILED:
serverLog(LL_NOTICE, "Migrate task %s failed for slots: %s", task->id, str);
@@ -1614,7 +1628,7 @@ void asmSyncWithSource(connection *conn) {
/* Create RDB channel connection */
clusterNode *source_node = clusterLookupNode(task->source, CLUSTER_NAMELEN);
if (!source_node) {
task_error_msg = sdscatfmt(sdsempty(), "Source node %.40s was not found", task->source);
task_error_msg = sdscatprintf(sdsempty(), "Source node %.40s was not found", task->source);
goto error;
}
char *ip = clusterNodeIp(source_node);
@@ -3033,7 +3047,7 @@ void asmTriggerBackgroundTrim(asmTrimCtx *trim_ctx, int migration_cleanup) {
CLUSTER_SLOT_MASK_BITS,
KVSTORE_ALLOCATE_DICTS_ON_DEMAND);
estore *subexpires = estoreCreate(&subexpiresBucketsType, CLUSTER_SLOT_MASK_BITS);
dict *stream_idmp_keys = dictCreate(&objectKeyPointerValueDictType);
dict *stream_idmp_keys = dictCreate(&objectKeyNoValueDictType);
size_t total_keys = 0;
@@ -3648,7 +3662,7 @@ void asmActiveTrimDeleteKey(redisDb *db, robj *keyobj, int migration_cleanup) {
* to another node. The modules need to know that these keys are no longer
* available locally, so just send the keyspace notification to the modules,
* but not to clients. */
moduleNotifyKeyspaceEvent(NOTIFY_KEY_TRIMMED, "key_trimmed", keyobj, db->id);
moduleNotifyKeyspaceEvent(NOTIFY_KEY_TRIMMED, "key_trimmed", keyobj, db->id, NULL, 0);
} else {
/* Not a migration cleanup, the key is really deleted from the database,
* need to notify the clients. */
+1 -1
View File
@@ -37,7 +37,7 @@ struct slotRangeArray *asmTaskGetSlotRanges(const char *task_id);
int asmNotifyConfigUpdated(struct asmTask *task, sds *err);
size_t asmGetPeakSyncBufferSize(void);
size_t asmGetImportInputBufferSize(void);
size_t asmGetMigrateOutputBufferSize(void);
size_t asmGetMigrateOutputMemoryUsage(void);
int clusterAsmCancel(const char *task_id, const char *reason);
int clusterAsmCancelBySlot(int slot, const char *reason);
int clusterAsmCancelBySlotRangeArray(struct slotRangeArray *slots, const char *reason);
+672 -47
View File
@@ -24,12 +24,545 @@ const char *COMMAND_GROUP_STR[] = {
"geo",
"stream",
"bitmap",
"module"
"array",
"module",
#ifdef ENABLE_GCRA
"rate_limit"
#endif
};
const char *commandGroupStr(int index) {
return COMMAND_GROUP_STR[index];
}
/********** ARCOUNT ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARCOUNT history */
#define ARCOUNT_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARCOUNT tips */
#define ARCOUNT_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARCOUNT key specs */
keySpec ARCOUNT_Keyspecs[1] = {
{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARCOUNT argument table */
struct COMMAND_ARG ARCOUNT_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/********** ARDEL ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARDEL history */
#define ARDEL_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARDEL tips */
#define ARDEL_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARDEL key specs */
keySpec ARDEL_Keyspecs[1] = {
{NULL,CMD_KEY_RW|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARDEL argument table */
struct COMMAND_ARG ARDEL_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("index",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
};
/********** ARDELRANGE ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARDELRANGE history */
#define ARDELRANGE_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARDELRANGE tips */
#define ARDELRANGE_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARDELRANGE key specs */
keySpec ARDELRANGE_Keyspecs[1] = {
{NULL,CMD_KEY_RW|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARDELRANGE range argument table */
struct COMMAND_ARG ARDELRANGE_range_Subargs[] = {
{MAKE_ARG("start",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("end",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/* ARDELRANGE argument table */
struct COMMAND_ARG ARDELRANGE_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("range",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,2,NULL),.subargs=ARDELRANGE_range_Subargs},
};
/********** ARGET ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARGET history */
#define ARGET_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARGET tips */
#define ARGET_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARGET key specs */
keySpec ARGET_Keyspecs[1] = {
{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARGET argument table */
struct COMMAND_ARG ARGET_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("index",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/********** ARGETRANGE ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARGETRANGE history */
#define ARGETRANGE_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARGETRANGE tips */
#define ARGETRANGE_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARGETRANGE key specs */
keySpec ARGETRANGE_Keyspecs[1] = {
{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARGETRANGE argument table */
struct COMMAND_ARG ARGETRANGE_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("start",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("end",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/********** ARGREP ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARGREP history */
#define ARGREP_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARGREP tips */
#define ARGREP_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARGREP key specs */
keySpec ARGREP_Keyspecs[1] = {
{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARGREP predicate exact argument table */
struct COMMAND_ARG ARGREP_predicate_exact_Subargs[] = {
{MAKE_ARG("exact",ARG_TYPE_PURE_TOKEN,-1,"EXACT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("string",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/* ARGREP predicate match argument table */
struct COMMAND_ARG ARGREP_predicate_match_Subargs[] = {
{MAKE_ARG("match",ARG_TYPE_PURE_TOKEN,-1,"MATCH",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("string",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/* ARGREP predicate glob argument table */
struct COMMAND_ARG ARGREP_predicate_glob_Subargs[] = {
{MAKE_ARG("glob",ARG_TYPE_PURE_TOKEN,-1,"GLOB",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("pattern",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/* ARGREP predicate re argument table */
struct COMMAND_ARG ARGREP_predicate_re_Subargs[] = {
{MAKE_ARG("re",ARG_TYPE_PURE_TOKEN,-1,"RE",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("pattern",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/* ARGREP predicate argument table */
struct COMMAND_ARG ARGREP_predicate_Subargs[] = {
{MAKE_ARG("exact",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=ARGREP_predicate_exact_Subargs},
{MAKE_ARG("match",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=ARGREP_predicate_match_Subargs},
{MAKE_ARG("glob",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=ARGREP_predicate_glob_Subargs},
{MAKE_ARG("re",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=ARGREP_predicate_re_Subargs},
};
/* ARGREP options argument table */
struct COMMAND_ARG ARGREP_options_Subargs[] = {
{MAKE_ARG("and",ARG_TYPE_PURE_TOKEN,-1,"AND",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("or",ARG_TYPE_PURE_TOKEN,-1,"OR",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("limit",ARG_TYPE_INTEGER,-1,"LIMIT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("withvalues",ARG_TYPE_PURE_TOKEN,-1,"WITHVALUES",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("nocase",ARG_TYPE_PURE_TOKEN,-1,"NOCASE",NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/* ARGREP argument table */
struct COMMAND_ARG ARGREP_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("start",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("end",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("predicate",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,4,NULL),.subargs=ARGREP_predicate_Subargs},
{MAKE_ARG("options",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,5,NULL),.subargs=ARGREP_options_Subargs},
};
/********** ARINFO ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARINFO history */
#define ARINFO_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARINFO tips */
#define ARINFO_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARINFO key specs */
keySpec ARINFO_Keyspecs[1] = {
{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARINFO argument table */
struct COMMAND_ARG ARINFO_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("full",ARG_TYPE_PURE_TOKEN,-1,"FULL",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)},
};
/********** ARINSERT ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARINSERT history */
#define ARINSERT_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARINSERT tips */
#define ARINSERT_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARINSERT key specs */
keySpec ARINSERT_Keyspecs[1] = {
{NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARINSERT argument table */
struct COMMAND_ARG ARINSERT_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_MULTIPLE,0,NULL)},
};
/********** ARLASTITEMS ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARLASTITEMS history */
#define ARLASTITEMS_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARLASTITEMS tips */
#define ARLASTITEMS_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARLASTITEMS key specs */
keySpec ARLASTITEMS_Keyspecs[1] = {
{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARLASTITEMS argument table */
struct COMMAND_ARG ARLASTITEMS_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("count",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("rev",ARG_TYPE_PURE_TOKEN,-1,"REV",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)},
};
/********** ARLEN ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARLEN history */
#define ARLEN_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARLEN tips */
#define ARLEN_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARLEN key specs */
keySpec ARLEN_Keyspecs[1] = {
{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARLEN argument table */
struct COMMAND_ARG ARLEN_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/********** ARMGET ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARMGET history */
#define ARMGET_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARMGET tips */
#define ARMGET_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARMGET key specs */
keySpec ARMGET_Keyspecs[1] = {
{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARMGET argument table */
struct COMMAND_ARG ARMGET_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("index",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
};
/********** ARMSET ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARMSET history */
#define ARMSET_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARMSET tips */
#define ARMSET_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARMSET key specs */
keySpec ARMSET_Keyspecs[1] = {
{NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARMSET data argument table */
struct COMMAND_ARG ARMSET_data_Subargs[] = {
{MAKE_ARG("index",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/* ARMSET argument table */
struct COMMAND_ARG ARMSET_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("data",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,2,NULL),.subargs=ARMSET_data_Subargs},
};
/********** ARNEXT ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARNEXT history */
#define ARNEXT_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARNEXT tips */
#define ARNEXT_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARNEXT key specs */
keySpec ARNEXT_Keyspecs[1] = {
{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARNEXT argument table */
struct COMMAND_ARG ARNEXT_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/********** AROP ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* AROP history */
#define AROP_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* AROP tips */
#define AROP_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* AROP key specs */
keySpec AROP_Keyspecs[1] = {
{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* AROP operation match argument table */
struct COMMAND_ARG AROP_operation_match_Subargs[] = {
{MAKE_ARG("match",ARG_TYPE_PURE_TOKEN,-1,"MATCH",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/* AROP operation argument table */
struct COMMAND_ARG AROP_operation_Subargs[] = {
{MAKE_ARG("sum",ARG_TYPE_PURE_TOKEN,-1,"SUM",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("min",ARG_TYPE_PURE_TOKEN,-1,"MIN",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("max",ARG_TYPE_PURE_TOKEN,-1,"MAX",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("and",ARG_TYPE_PURE_TOKEN,-1,"AND",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("or",ARG_TYPE_PURE_TOKEN,-1,"OR",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("xor",ARG_TYPE_PURE_TOKEN,-1,"XOR",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("match",ARG_TYPE_BLOCK,-1,NULL,NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=AROP_operation_match_Subargs},
{MAKE_ARG("used",ARG_TYPE_PURE_TOKEN,-1,"USED",NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/* AROP argument table */
struct COMMAND_ARG AROP_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("start",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("end",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("operation",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,8,NULL),.subargs=AROP_operation_Subargs},
};
/********** ARRING ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARRING history */
#define ARRING_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARRING tips */
#define ARRING_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARRING key specs */
keySpec ARRING_Keyspecs[1] = {
{NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARRING argument table */
struct COMMAND_ARG ARRING_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("size",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
};
/********** ARSCAN ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARSCAN history */
#define ARSCAN_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARSCAN tips */
#define ARSCAN_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARSCAN key specs */
keySpec ARSCAN_Keyspecs[1] = {
{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARSCAN argument table */
struct COMMAND_ARG ARSCAN_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("start",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("end",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("limit",ARG_TYPE_INTEGER,-1,"LIMIT",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)},
};
/********** ARSEEK ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARSEEK history */
#define ARSEEK_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARSEEK tips */
#define ARSEEK_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARSEEK key specs */
keySpec ARSEEK_Keyspecs[1] = {
{NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARSEEK argument table */
struct COMMAND_ARG ARSEEK_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("index",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/********** ARSET ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* ARSET history */
#define ARSET_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* ARSET tips */
#define ARSET_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* ARSET key specs */
keySpec ARSET_Keyspecs[1] = {
{NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* ARSET argument table */
struct COMMAND_ARG ARSET_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("index",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("value",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
};
/********** BITCOUNT ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
@@ -9165,7 +9698,9 @@ struct COMMAND_ARG ZINCRBY_Args[] = {
#ifndef SKIP_CMD_HISTORY_TABLE
/* ZINTER history */
#define ZINTER_History NULL
commandHistory ZINTER_History[] = {
{"8.8.0","Added `COUNT` aggregate option."},
};
#endif
#ifndef SKIP_CMD_TIPS_TABLE
@@ -9185,6 +9720,7 @@ struct COMMAND_ARG ZINTER_aggregate_Subargs[] = {
{MAKE_ARG("sum",ARG_TYPE_PURE_TOKEN,-1,"SUM",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("min",ARG_TYPE_PURE_TOKEN,-1,"MIN",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("max",ARG_TYPE_PURE_TOKEN,-1,"MAX",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("count",ARG_TYPE_PURE_TOKEN,-1,"COUNT",NULL,"8.8.0",CMD_ARG_NONE,0,NULL)},
};
/* ZINTER argument table */
@@ -9192,7 +9728,7 @@ struct COMMAND_ARG ZINTER_Args[] = {
{MAKE_ARG("numkeys",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
{MAKE_ARG("weight",ARG_TYPE_INTEGER,-1,"WEIGHTS",NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)},
{MAKE_ARG("aggregate",ARG_TYPE_ONEOF,-1,"AGGREGATE",NULL,NULL,CMD_ARG_OPTIONAL,3,NULL),.subargs=ZINTER_aggregate_Subargs},
{MAKE_ARG("aggregate",ARG_TYPE_ONEOF,-1,"AGGREGATE",NULL,NULL,CMD_ARG_OPTIONAL,4,NULL),.subargs=ZINTER_aggregate_Subargs},
{MAKE_ARG("withscores",ARG_TYPE_PURE_TOKEN,-1,"WITHSCORES",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)},
};
@@ -9226,7 +9762,9 @@ struct COMMAND_ARG ZINTERCARD_Args[] = {
#ifndef SKIP_CMD_HISTORY_TABLE
/* ZINTERSTORE history */
#define ZINTERSTORE_History NULL
commandHistory ZINTERSTORE_History[] = {
{"8.8.0","Added `COUNT` aggregate option."},
};
#endif
#ifndef SKIP_CMD_TIPS_TABLE
@@ -9246,6 +9784,7 @@ struct COMMAND_ARG ZINTERSTORE_aggregate_Subargs[] = {
{MAKE_ARG("sum",ARG_TYPE_PURE_TOKEN,-1,"SUM",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("min",ARG_TYPE_PURE_TOKEN,-1,"MIN",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("max",ARG_TYPE_PURE_TOKEN,-1,"MAX",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("count",ARG_TYPE_PURE_TOKEN,-1,"COUNT",NULL,"8.8.0",CMD_ARG_NONE,0,NULL)},
};
/* ZINTERSTORE argument table */
@@ -9254,7 +9793,7 @@ struct COMMAND_ARG ZINTERSTORE_Args[] = {
{MAKE_ARG("numkeys",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("key",ARG_TYPE_KEY,1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
{MAKE_ARG("weight",ARG_TYPE_INTEGER,-1,"WEIGHTS",NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)},
{MAKE_ARG("aggregate",ARG_TYPE_ONEOF,-1,"AGGREGATE",NULL,NULL,CMD_ARG_OPTIONAL,3,NULL),.subargs=ZINTERSTORE_aggregate_Subargs},
{MAKE_ARG("aggregate",ARG_TYPE_ONEOF,-1,"AGGREGATE",NULL,NULL,CMD_ARG_OPTIONAL,4,NULL),.subargs=ZINTERSTORE_aggregate_Subargs},
};
/********** ZLEXCOUNT ********************/
@@ -9894,7 +10433,9 @@ struct COMMAND_ARG ZSCORE_Args[] = {
#ifndef SKIP_CMD_HISTORY_TABLE
/* ZUNION history */
#define ZUNION_History NULL
commandHistory ZUNION_History[] = {
{"8.8.0","Added `COUNT` aggregate option."},
};
#endif
#ifndef SKIP_CMD_TIPS_TABLE
@@ -9914,6 +10455,7 @@ struct COMMAND_ARG ZUNION_aggregate_Subargs[] = {
{MAKE_ARG("sum",ARG_TYPE_PURE_TOKEN,-1,"SUM",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("min",ARG_TYPE_PURE_TOKEN,-1,"MIN",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("max",ARG_TYPE_PURE_TOKEN,-1,"MAX",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("count",ARG_TYPE_PURE_TOKEN,-1,"COUNT",NULL,"8.8.0",CMD_ARG_NONE,0,NULL)},
};
/* ZUNION argument table */
@@ -9921,7 +10463,7 @@ struct COMMAND_ARG ZUNION_Args[] = {
{MAKE_ARG("numkeys",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
{MAKE_ARG("weight",ARG_TYPE_INTEGER,-1,"WEIGHTS",NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)},
{MAKE_ARG("aggregate",ARG_TYPE_ONEOF,-1,"AGGREGATE",NULL,NULL,CMD_ARG_OPTIONAL,3,NULL),.subargs=ZUNION_aggregate_Subargs},
{MAKE_ARG("aggregate",ARG_TYPE_ONEOF,-1,"AGGREGATE",NULL,NULL,CMD_ARG_OPTIONAL,4,NULL),.subargs=ZUNION_aggregate_Subargs},
{MAKE_ARG("withscores",ARG_TYPE_PURE_TOKEN,-1,"WITHSCORES",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)},
};
@@ -9929,7 +10471,9 @@ struct COMMAND_ARG ZUNION_Args[] = {
#ifndef SKIP_CMD_HISTORY_TABLE
/* ZUNIONSTORE history */
#define ZUNIONSTORE_History NULL
commandHistory ZUNIONSTORE_History[] = {
{"8.8.0","Added `COUNT` aggregate option."},
};
#endif
#ifndef SKIP_CMD_TIPS_TABLE
@@ -9949,6 +10493,7 @@ struct COMMAND_ARG ZUNIONSTORE_aggregate_Subargs[] = {
{MAKE_ARG("sum",ARG_TYPE_PURE_TOKEN,-1,"SUM",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("min",ARG_TYPE_PURE_TOKEN,-1,"MIN",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("max",ARG_TYPE_PURE_TOKEN,-1,"MAX",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("count",ARG_TYPE_PURE_TOKEN,-1,"COUNT",NULL,"8.8.0",CMD_ARG_NONE,0,NULL)},
};
/* ZUNIONSTORE argument table */
@@ -9957,7 +10502,7 @@ struct COMMAND_ARG ZUNIONSTORE_Args[] = {
{MAKE_ARG("numkeys",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("key",ARG_TYPE_KEY,1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
{MAKE_ARG("weight",ARG_TYPE_INTEGER,-1,"WEIGHTS",NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,0,NULL)},
{MAKE_ARG("aggregate",ARG_TYPE_ONEOF,-1,"AGGREGATE",NULL,NULL,CMD_ARG_OPTIONAL,3,NULL),.subargs=ZUNIONSTORE_aggregate_Subargs},
{MAKE_ARG("aggregate",ARG_TYPE_ONEOF,-1,"AGGREGATE",NULL,NULL,CMD_ARG_OPTIONAL,4,NULL),.subargs=ZUNIONSTORE_aggregate_Subargs},
};
/********** XACK ********************/
@@ -10574,6 +11119,7 @@ commandHistory XINFO_STREAM_History[] = {
{"7.0.0","Added the `max-deleted-entry-id`, `entries-added`, `recorded-first-entry-id`, `entries-read` and `lag` fields"},
{"7.2.0","Added the `active-time` field, and changed the meaning of `seen-time`."},
{"8.6.0","Added the `idmp-duration`, `idmp-maxsize`, `pids-tracked`, `iids-tracked`, `iids-added` and `iids-duplicates` fields for IDMP tracking."},
{"8.8.0","Added the `nacked-count` field to consumer groups in `FULL` output."},
};
#endif
@@ -10606,7 +11152,7 @@ struct COMMAND_STRUCT XINFO_Subcommands[] = {
{MAKE_CMD("consumers","Returns a list of the consumers in a consumer group.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XINFO_CONSUMERS_History,1,XINFO_CONSUMERS_Tips,1,xinfoCommand,4,CMD_READONLY,ACL_CATEGORY_STREAM,XINFO_CONSUMERS_Keyspecs,1,NULL,2),.args=XINFO_CONSUMERS_Args},
{MAKE_CMD("groups","Returns a list of the consumer groups of a stream.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XINFO_GROUPS_History,1,XINFO_GROUPS_Tips,0,xinfoCommand,3,CMD_READONLY,ACL_CATEGORY_STREAM,XINFO_GROUPS_Keyspecs,1,NULL,1),.args=XINFO_GROUPS_Args},
{MAKE_CMD("help","Returns helpful text about the different subcommands.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XINFO_HELP_History,0,XINFO_HELP_Tips,0,xinfoCommand,2,CMD_LOADING|CMD_STALE,ACL_CATEGORY_STREAM,XINFO_HELP_Keyspecs,0,NULL,0)},
{MAKE_CMD("stream","Returns information about a stream.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XINFO_STREAM_History,4,XINFO_STREAM_Tips,0,xinfoCommand,-3,CMD_READONLY,ACL_CATEGORY_STREAM,XINFO_STREAM_Keyspecs,1,NULL,2),.args=XINFO_STREAM_Args},
{MAKE_CMD("stream","Returns information about a stream.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XINFO_STREAM_History,5,XINFO_STREAM_Tips,0,xinfoCommand,-3,CMD_READONLY,ACL_CATEGORY_STREAM,XINFO_STREAM_Keyspecs,1,NULL,2),.args=XINFO_STREAM_Args},
{0}
};
@@ -10651,6 +11197,48 @@ struct COMMAND_ARG XLEN_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/********** XNACK ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* XNACK history */
#define XNACK_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* XNACK tips */
#define XNACK_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* XNACK key specs */
keySpec XNACK_Keyspecs[1] = {
{NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* XNACK mode argument table */
struct COMMAND_ARG XNACK_mode_Subargs[] = {
{MAKE_ARG("silent",ARG_TYPE_PURE_TOKEN,-1,"SILENT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("fail",ARG_TYPE_PURE_TOKEN,-1,"FAIL",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("fatal",ARG_TYPE_PURE_TOKEN,-1,"FATAL",NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/* XNACK ids argument table */
struct COMMAND_ARG XNACK_ids_Subargs[] = {
{MAKE_ARG("numids",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("id",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
};
/* XNACK argument table */
struct COMMAND_ARG XNACK_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("group",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("mode",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,3,NULL),.subargs=XNACK_mode_Subargs},
{MAKE_ARG("ids",ARG_TYPE_BLOCK,-1,"IDS",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=XNACK_ids_Subargs},
{MAKE_ARG("count",ARG_TYPE_INTEGER,-1,"RETRYCOUNT",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)},
{MAKE_ARG("force",ARG_TYPE_PURE_TOKEN,-1,"FORCE",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)},
};
/********** XPENDING ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
@@ -11039,34 +11627,6 @@ struct COMMAND_ARG DIGEST_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/********** GCRA ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* GCRA history */
#define GCRA_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* GCRA tips */
#define GCRA_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* GCRA key specs */
keySpec GCRA_Keyspecs[1] = {
{NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* GCRA argument table */
struct COMMAND_ARG GCRA_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("max-burst",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("requests-per-period",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("period",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("count",ARG_TYPE_INTEGER,-1,"NUM_REQUESTS",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)},
};
/********** GET ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
@@ -11274,6 +11834,51 @@ struct COMMAND_ARG INCRBYFLOAT_Args[] = {
{MAKE_ARG("increment",ARG_TYPE_DOUBLE,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/********** INCREX ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
/* INCREX history */
#define INCREX_History NULL
#endif
#ifndef SKIP_CMD_TIPS_TABLE
/* INCREX tips */
#define INCREX_Tips NULL
#endif
#ifndef SKIP_CMD_KEY_SPECS_TABLE
/* INCREX key specs */
keySpec INCREX_Keyspecs[1] = {
{NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
};
#endif
/* INCREX increment argument table */
struct COMMAND_ARG INCREX_increment_Subargs[] = {
{MAKE_ARG("float",ARG_TYPE_DOUBLE,-1,"BYFLOAT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("integer",ARG_TYPE_INTEGER,-1,"BYINT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/* INCREX expiration argument table */
struct COMMAND_ARG INCREX_expiration_Subargs[] = {
{MAKE_ARG("seconds",ARG_TYPE_INTEGER,-1,"EX",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("milliseconds",ARG_TYPE_INTEGER,-1,"PX",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("unix-time-seconds",ARG_TYPE_UNIX_TIME,-1,"EXAT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("unix-time-milliseconds",ARG_TYPE_UNIX_TIME,-1,"PXAT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("persist",ARG_TYPE_PURE_TOKEN,-1,"PERSIST",NULL,NULL,CMD_ARG_NONE,0,NULL)},
};
/* INCREX argument table */
struct COMMAND_ARG INCREX_Args[] = {
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
{MAKE_ARG("increment",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=INCREX_increment_Subargs},
{MAKE_ARG("saturate",ARG_TYPE_PURE_TOKEN,-1,"SATURATE","Saturate the result to LBOUND/UBOUND (or the type limits when no explicit bound is given) when out of bounds. Without this option, out-of-bounds operations are rejected and reply [current_value, 0].",NULL,CMD_ARG_OPTIONAL,0,NULL)},
{MAKE_ARG("lowerbound",ARG_TYPE_STRING,-1,"LBOUND","Integer when used with BYINT, floating-point when used with BYFLOAT.",NULL,CMD_ARG_OPTIONAL,0,NULL)},
{MAKE_ARG("upperbound",ARG_TYPE_STRING,-1,"UBOUND","Integer when used with BYINT, floating-point when used with BYFLOAT.",NULL,CMD_ARG_OPTIONAL,0,NULL)},
{MAKE_ARG("expiration",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,5,NULL),.subargs=INCREX_expiration_Subargs},
{MAKE_ARG("enx",ARG_TYPE_PURE_TOKEN,-1,"ENX","Only set the expiration if the key currently has no TTL. Requires one of EX/PX/EXAT/PXAT; cannot be combined with PERSIST.",NULL,CMD_ARG_OPTIONAL,0,NULL)},
};
/********** LCS ********************/
#ifndef SKIP_CMD_HISTORY_TABLE
@@ -11743,6 +12348,25 @@ struct COMMAND_ARG WATCH_Args[] = {
/* Main command table */
struct COMMAND_STRUCT redisCommandTable[] = {
/* array */
{MAKE_CMD("arcount","Returns the number of non-empty elements in an array.","O(1)","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARCOUNT_History,0,ARCOUNT_Tips,0,arcountCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_ARRAY,ARCOUNT_Keyspecs,1,NULL,1),.args=ARCOUNT_Args},
{MAKE_CMD("ardel","Deletes elements at the specified indices in an array.","O(N) where N is the number of indices to delete","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARDEL_History,0,ARDEL_Tips,0,ardelCommand,-3,CMD_WRITE|CMD_FAST,ACL_CATEGORY_ARRAY,ARDEL_Keyspecs,1,NULL,2),.args=ARDEL_Args},
{MAKE_CMD("ardelrange","Deletes elements in one or more ranges.","Proportional to the number of existing elements / slices touched, not to the numeric span of the requested ranges","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARDELRANGE_History,0,ARDELRANGE_Tips,0,ardelrangeCommand,-4,CMD_WRITE,ACL_CATEGORY_ARRAY,ARDELRANGE_Keyspecs,1,NULL,2),.args=ARDELRANGE_Args},
{MAKE_CMD("arget","Gets the value at an index in an array.","O(1)","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARGET_History,0,ARGET_Tips,0,argetCommand,3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_ARRAY,ARGET_Keyspecs,1,NULL,2),.args=ARGET_Args},
{MAKE_CMD("argetrange","Gets values in a range of indices.","O(N) where N is the range length","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARGETRANGE_History,0,ARGETRANGE_Tips,0,argetrangeCommand,4,CMD_READONLY,ACL_CATEGORY_ARRAY,ARGETRANGE_Keyspecs,1,NULL,3),.args=ARGETRANGE_Args},
{MAKE_CMD("argrep","Searches array elements in a range using textual predicates.","O(P * C) where P is the number of visited positions in touched slices and C is the cost of evaluating the predicates on one existing element.","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARGREP_History,0,ARGREP_Tips,0,argrepCommand,-6,CMD_READONLY,ACL_CATEGORY_ARRAY,ARGREP_Keyspecs,1,NULL,5),.args=ARGREP_Args},
{MAKE_CMD("arinfo","Returns metadata about an array.","O(1), or O(N) with FULL option where N is the number of slices.","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARINFO_History,0,ARINFO_Tips,0,arinfoCommand,-2,CMD_READONLY,ACL_CATEGORY_ARRAY,ARINFO_Keyspecs,1,NULL,2),.args=ARINFO_Args},
{MAKE_CMD("arinsert","Inserts one or more values at consecutive indices.","O(N) where N is the number of values","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARINSERT_History,0,ARINSERT_Tips,0,arinsertCommand,-3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_ARRAY,ARINSERT_Keyspecs,1,NULL,2),.args=ARINSERT_Args},
{MAKE_CMD("arlastitems","Returns the most recently inserted elements.","O(N) where N is the count","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARLASTITEMS_History,0,ARLASTITEMS_Tips,0,arlastitemsCommand,-3,CMD_READONLY,ACL_CATEGORY_ARRAY,ARLASTITEMS_Keyspecs,1,NULL,3),.args=ARLASTITEMS_Args},
{MAKE_CMD("arlen","Returns the length of an array (max index + 1).","O(1)","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARLEN_History,0,ARLEN_Tips,0,arlenCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_ARRAY,ARLEN_Keyspecs,1,NULL,1),.args=ARLEN_Args},
{MAKE_CMD("armget","Gets values at multiple indices in an array.","O(N) where N is the number of indices","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARMGET_History,0,ARMGET_Tips,0,armgetCommand,-3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_ARRAY,ARMGET_Keyspecs,1,NULL,2),.args=ARMGET_Args},
{MAKE_CMD("armset","Sets multiple index-value pairs in an array.","O(N) where N is the number of pairs","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARMSET_History,0,ARMSET_Tips,0,armsetCommand,-4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_ARRAY,ARMSET_Keyspecs,1,NULL,2),.args=ARMSET_Args},
{MAKE_CMD("arnext","Returns the next index ARINSERT would use.","O(1)","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARNEXT_History,0,ARNEXT_Tips,0,arnextCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_ARRAY,ARNEXT_Keyspecs,1,NULL,1),.args=ARNEXT_Args},
{MAKE_CMD("arop","Performs aggregate operations on array elements in a range.","O(P) where P is visited positions in touched slices (dense scanned slots + sparse entries), with worst-case O(|end-start|+1) and typical case close to O(N), where N is the number of existing elements in range.","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,AROP_History,0,AROP_Tips,0,aropCommand,-5,CMD_READONLY,ACL_CATEGORY_ARRAY,AROP_Keyspecs,1,NULL,4),.args=AROP_Args},
{MAKE_CMD("arring","Inserts values into a ring buffer of specified size, wrapping and truncating as needed.","O(M) normally, O(N+M) on ring resize, where N is the maximum of the old and new ring size and M is the number of inserted values","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARRING_History,0,ARRING_Tips,0,arringCommand,-4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_ARRAY,ARRING_Keyspecs,1,NULL,3),.args=ARRING_Args},
{MAKE_CMD("arscan","Iterates existing elements in a range, returning index-value pairs.","O(P) where P is visited positions in touched slices (dense scanned slots + sparse entries), with worst-case O(|end-start|+1) and typical case close to O(N), where N is the number of existing elements in range.","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARSCAN_History,0,ARSCAN_Tips,0,arscanCommand,-4,CMD_READONLY,ACL_CATEGORY_ARRAY,ARSCAN_Keyspecs,1,NULL,4),.args=ARSCAN_Args},
{MAKE_CMD("arseek","Sets the ARINSERT / ARRING cursor to a specific index.","O(1)","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARSEEK_History,0,ARSEEK_Tips,0,arseekCommand,3,CMD_WRITE|CMD_FAST,ACL_CATEGORY_ARRAY,ARSEEK_Keyspecs,1,NULL,2),.args=ARSEEK_Args},
{MAKE_CMD("arset","Sets one or more contiguous values starting at an index in an array.","O(N) where N is the number of values","8.8.0",CMD_DOC_NONE,NULL,NULL,"array",COMMAND_GROUP_ARRAY,ARSET_History,0,ARSET_Tips,0,arsetCommand,-4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_ARRAY,ARSET_Keyspecs,1,NULL,3),.args=ARSET_Args},
/* bitmap */
{MAKE_CMD("bitcount","Counts the number of set bits (population counting) in a string.","O(N)","2.6.0",CMD_DOC_NONE,NULL,NULL,"bitmap",COMMAND_GROUP_BITMAP,BITCOUNT_History,1,BITCOUNT_Tips,0,bitcountCommand,-2,CMD_READONLY,ACL_CATEGORY_BITMAP,BITCOUNT_Keyspecs,1,NULL,2),.args=BITCOUNT_Args},
{MAKE_CMD("bitfield","Performs arbitrary bitfield integer operations on strings.","O(1) for each subcommand specified","3.2.0",CMD_DOC_NONE,NULL,NULL,"bitmap",COMMAND_GROUP_BITMAP,BITFIELD_History,0,BITFIELD_Tips,0,bitfieldCommand,-2,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_BITMAP,BITFIELD_Keyspecs,1,bitfieldGetKeys,2),.args=BITFIELD_Args},
@@ -11839,7 +12463,7 @@ struct COMMAND_STRUCT redisCommandTable[] = {
{MAKE_CMD("pfadd","Adds elements to a HyperLogLog key. Creates the key if it doesn't exist.","O(1) to add every element.","2.8.9",CMD_DOC_NONE,NULL,NULL,"hyperloglog",COMMAND_GROUP_HYPERLOGLOG,PFADD_History,0,PFADD_Tips,0,pfaddCommand,-2,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_HYPERLOGLOG,PFADD_Keyspecs,1,NULL,2),.args=PFADD_Args},
{MAKE_CMD("pfcount","Returns the approximated cardinality of the set(s) observed by the HyperLogLog key(s).","O(1) with a very small average constant time when called with a single key. O(N) with N being the number of keys, and much bigger constant times, when called with multiple keys.","2.8.9",CMD_DOC_NONE,NULL,NULL,"hyperloglog",COMMAND_GROUP_HYPERLOGLOG,PFCOUNT_History,0,PFCOUNT_Tips,0,pfcountCommand,-2,CMD_READONLY|CMD_MAY_REPLICATE,ACL_CATEGORY_HYPERLOGLOG,PFCOUNT_Keyspecs,1,NULL,1),.args=PFCOUNT_Args},
{MAKE_CMD("pfdebug","Internal commands for debugging HyperLogLog values.","N/A","2.8.9",CMD_DOC_SYSCMD,NULL,NULL,"hyperloglog",COMMAND_GROUP_HYPERLOGLOG,PFDEBUG_History,0,PFDEBUG_Tips,0,pfdebugCommand,3,CMD_WRITE|CMD_DENYOOM|CMD_ADMIN,ACL_CATEGORY_HYPERLOGLOG,PFDEBUG_Keyspecs,1,NULL,2),.args=PFDEBUG_Args},
{MAKE_CMD("pfmerge","Merges one or more HyperLogLog values into a single key.","O(N) to merge N HyperLogLogs, but with high constant times.","2.8.9",CMD_DOC_NONE,NULL,NULL,"hyperloglog",COMMAND_GROUP_HYPERLOGLOG,PFMERGE_History,0,PFMERGE_Tips,0,pfmergeCommand,-2,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_HYPERLOGLOG,PFMERGE_Keyspecs,2,NULL,2),.args=PFMERGE_Args},
{MAKE_CMD("pfmerge","Merges one or more HyperLogLog values into a single key.","O(N) to merge N HyperLogLogs, but with high constant times.","2.8.9",CMD_DOC_NONE,NULL,NULL,"hyperloglog",COMMAND_GROUP_HYPERLOGLOG,PFMERGE_History,0,PFMERGE_Tips,0,pfmergeCommand,-2,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_HYPERLOGLOG,PFMERGE_Keyspecs,2,pfmergeGetKeys,2),.args=PFMERGE_Args},
{MAKE_CMD("pfselftest","An internal command for testing HyperLogLog values.","N/A","2.8.9",CMD_DOC_SYSCMD,NULL,NULL,"hyperloglog",COMMAND_GROUP_HYPERLOGLOG,PFSELFTEST_History,0,PFSELFTEST_Tips,0,pfselftestCommand,1,CMD_ADMIN,ACL_CATEGORY_HYPERLOGLOG,PFSELFTEST_Keyspecs,0,NULL,0)},
/* list */
{MAKE_CMD("blmove","Pops an element from a list, pushes it to another list and returns it. Blocks until an element is available otherwise. Deletes the list if the last element was moved.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,BLMOVE_History,0,BLMOVE_Tips,0,blmoveCommand,6,CMD_WRITE|CMD_DENYOOM|CMD_BLOCKING,ACL_CATEGORY_LIST,BLMOVE_Keyspecs,2,NULL,5),.args=BLMOVE_Args},
@@ -11865,13 +12489,13 @@ struct COMMAND_STRUCT redisCommandTable[] = {
{MAKE_CMD("rpush","Appends one or more elements to a list. Creates the key if it doesn't exist.","O(1) for each element added, so O(N) to add N elements when the command is called with multiple arguments.","1.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,RPUSH_History,1,RPUSH_Tips,0,rpushCommand,-3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_LIST,RPUSH_Keyspecs,1,NULL,2),.args=RPUSH_Args},
{MAKE_CMD("rpushx","Appends an element to a list only when the list exists.","O(1) for each element added, so O(N) to add N elements when the command is called with multiple arguments.","2.2.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,RPUSHX_History,1,RPUSHX_Tips,0,rpushxCommand,-3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_LIST,RPUSHX_Keyspecs,1,NULL,2),.args=RPUSHX_Args},
/* pubsub */
{MAKE_CMD("psubscribe","Listens for messages published to channels that match one or more patterns.","O(N) where N is the number of patterns to subscribe to.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PSUBSCRIBE_History,0,PSUBSCRIBE_Tips,0,psubscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,PSUBSCRIBE_Keyspecs,0,NULL,1),.args=PSUBSCRIBE_Args},
{MAKE_CMD("psubscribe","Listens for messages published to channels that match one or more patterns.","O(N) where N is the number of patterns to subscribe to.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PSUBSCRIBE_History,0,PSUBSCRIBE_Tips,0,psubscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL|CMD_DENYOOM,0,PSUBSCRIBE_Keyspecs,0,NULL,1),.args=PSUBSCRIBE_Args},
{MAKE_CMD("publish","Posts a message to a channel.","O(N+M) where N is the number of clients subscribed to the receiving channel and M is the total number of subscribed patterns (by any client).","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUBLISH_History,0,PUBLISH_Tips,0,publishCommand,3,CMD_PUBSUB|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_MAY_REPLICATE|CMD_SENTINEL,0,PUBLISH_Keyspecs,0,NULL,2),.args=PUBLISH_Args},
{MAKE_CMD("pubsub","A container for Pub/Sub commands.","Depends on subcommand.","2.8.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUBSUB_History,0,PUBSUB_Tips,0,NULL,-2,0,0,PUBSUB_Keyspecs,0,NULL,0),.subcommands=PUBSUB_Subcommands},
{MAKE_CMD("punsubscribe","Stops listening to messages published to channels that match one or more patterns.","O(N) where N is the number of patterns to unsubscribe.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUNSUBSCRIBE_History,0,PUNSUBSCRIBE_Tips,0,punsubscribeCommand,-1,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,PUNSUBSCRIBE_Keyspecs,0,NULL,1),.args=PUNSUBSCRIBE_Args},
{MAKE_CMD("spublish","Post a message to a shard channel","O(N) where N is the number of clients subscribed to the receiving shard channel.","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SPUBLISH_History,0,SPUBLISH_Tips,0,spublishCommand,3,CMD_PUBSUB|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_MAY_REPLICATE,0,SPUBLISH_Keyspecs,1,NULL,2),.args=SPUBLISH_Args},
{MAKE_CMD("ssubscribe","Listens for messages published to shard channels.","O(N) where N is the number of shard channels to subscribe to.","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SSUBSCRIBE_History,0,SSUBSCRIBE_Tips,0,ssubscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,0,SSUBSCRIBE_Keyspecs,1,NULL,1),.args=SSUBSCRIBE_Args},
{MAKE_CMD("subscribe","Listens for messages published to channels.","O(N) where N is the number of channels to subscribe to.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SUBSCRIBE_History,0,SUBSCRIBE_Tips,0,subscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,SUBSCRIBE_Keyspecs,0,NULL,1),.args=SUBSCRIBE_Args},
{MAKE_CMD("ssubscribe","Listens for messages published to shard channels.","O(N) where N is the number of shard channels to subscribe to.","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SSUBSCRIBE_History,0,SSUBSCRIBE_Tips,0,ssubscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_DENYOOM,0,SSUBSCRIBE_Keyspecs,1,NULL,1),.args=SSUBSCRIBE_Args},
{MAKE_CMD("subscribe","Listens for messages published to channels.","O(N) where N is the number of channels to subscribe to.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SUBSCRIBE_History,0,SUBSCRIBE_Tips,0,subscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL|CMD_DENYOOM,0,SUBSCRIBE_Keyspecs,0,NULL,1),.args=SUBSCRIBE_Args},
{MAKE_CMD("sunsubscribe","Stops listening to messages posted to shard channels.","O(N) where N is the number of shard channels to unsubscribe.","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SUNSUBSCRIBE_History,0,SUNSUBSCRIBE_Tips,0,sunsubscribeCommand,-1,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,0,SUNSUBSCRIBE_Keyspecs,1,NULL,1),.args=SUNSUBSCRIBE_Args},
{MAKE_CMD("unsubscribe","Stops listening to messages posted to channels.","O(N) where N is the number of channels to unsubscribe.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,UNSUBSCRIBE_History,0,UNSUBSCRIBE_Tips,0,unsubscribeCommand,-1,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,UNSUBSCRIBE_Keyspecs,0,NULL,1),.args=UNSUBSCRIBE_Args},
/* scripting */
@@ -11945,9 +12569,9 @@ struct COMMAND_STRUCT redisCommandTable[] = {
{MAKE_CMD("zdiff","Returns the difference between multiple sorted sets.","O(L + (N-K)log(N)) worst case where L is the total number of elements in all the sets, N is the size of the first set, and K is the size of the result set.","6.2.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZDIFF_History,0,ZDIFF_Tips,0,zdiffCommand,-3,CMD_READONLY,ACL_CATEGORY_SORTEDSET,ZDIFF_Keyspecs,1,zunionInterDiffGetKeys,3),.args=ZDIFF_Args},
{MAKE_CMD("zdiffstore","Stores the difference of multiple sorted sets in a key.","O(L + (N-K)log(N)) worst case where L is the total number of elements in all the sets, N is the size of the first set, and K is the size of the result set.","6.2.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZDIFFSTORE_History,0,ZDIFFSTORE_Tips,0,zdiffstoreCommand,-4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SORTEDSET,ZDIFFSTORE_Keyspecs,2,zunionInterDiffStoreGetKeys,3),.args=ZDIFFSTORE_Args},
{MAKE_CMD("zincrby","Increments the score of a member in a sorted set.","O(log(N)) where N is the number of elements in the sorted set.","1.2.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZINCRBY_History,0,ZINCRBY_Tips,0,zincrbyCommand,4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_SORTEDSET,ZINCRBY_Keyspecs,1,NULL,3),.args=ZINCRBY_Args},
{MAKE_CMD("zinter","Returns the intersect of multiple sorted sets.","O(N*K)+O(M*log(M)) worst case with N being the smallest input sorted set, K being the number of input sorted sets and M being the number of elements in the resulting sorted set.","6.2.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZINTER_History,0,ZINTER_Tips,0,zinterCommand,-3,CMD_READONLY,ACL_CATEGORY_SORTEDSET,ZINTER_Keyspecs,1,zunionInterDiffGetKeys,5),.args=ZINTER_Args},
{MAKE_CMD("zinter","Returns the intersect of multiple sorted sets.","O(N*K)+O(M*log(M)) worst case with N being the smallest input sorted set, K being the number of input sorted sets and M being the number of elements in the resulting sorted set.","6.2.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZINTER_History,1,ZINTER_Tips,0,zinterCommand,-3,CMD_READONLY,ACL_CATEGORY_SORTEDSET,ZINTER_Keyspecs,1,zunionInterDiffGetKeys,5),.args=ZINTER_Args},
{MAKE_CMD("zintercard","Returns the number of members of the intersect of multiple sorted sets.","O(N*K) worst case with N being the smallest input sorted set, K being the number of input sorted sets.","7.0.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZINTERCARD_History,0,ZINTERCARD_Tips,0,zinterCardCommand,-3,CMD_READONLY,ACL_CATEGORY_SORTEDSET,ZINTERCARD_Keyspecs,1,zunionInterDiffGetKeys,3),.args=ZINTERCARD_Args},
{MAKE_CMD("zinterstore","Stores the intersect of multiple sorted sets in a key.","O(N*K)+O(M*log(M)) worst case with N being the smallest input sorted set, K being the number of input sorted sets and M being the number of elements in the resulting sorted set.","2.0.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZINTERSTORE_History,0,ZINTERSTORE_Tips,0,zinterstoreCommand,-4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SORTEDSET,ZINTERSTORE_Keyspecs,2,zunionInterDiffStoreGetKeys,5),.args=ZINTERSTORE_Args},
{MAKE_CMD("zinterstore","Stores the intersect of multiple sorted sets in a key.","O(N*K)+O(M*log(M)) worst case with N being the smallest input sorted set, K being the number of input sorted sets and M being the number of elements in the resulting sorted set.","2.0.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZINTERSTORE_History,1,ZINTERSTORE_Tips,0,zinterstoreCommand,-4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SORTEDSET,ZINTERSTORE_Keyspecs,2,zunionInterDiffStoreGetKeys,5),.args=ZINTERSTORE_Args},
{MAKE_CMD("zlexcount","Returns the number of members in a sorted set within a lexicographical range.","O(log(N)) with N being the number of elements in the sorted set.","2.8.9",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZLEXCOUNT_History,0,ZLEXCOUNT_Tips,0,zlexcountCommand,4,CMD_READONLY|CMD_FAST,ACL_CATEGORY_SORTEDSET,ZLEXCOUNT_Keyspecs,1,NULL,3),.args=ZLEXCOUNT_Args},
{MAKE_CMD("zmpop","Returns the highest- or lowest-scoring members from one or more sorted sets after removing them. Deletes the sorted set if the last member was popped.","O(K) + O(M*log(N)) where K is the number of provided keys, N being the number of elements in the sorted set, and M being the number of elements popped.","7.0.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZMPOP_History,0,ZMPOP_Tips,0,zmpopCommand,-4,CMD_WRITE,ACL_CATEGORY_SORTEDSET,ZMPOP_Keyspecs,1,zmpopGetKeys,4),.args=ZMPOP_Args},
{MAKE_CMD("zmscore","Returns the score of one or more members in a sorted set.","O(N) where N is the number of members being requested.","6.2.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZMSCORE_History,0,ZMSCORE_Tips,0,zmscoreCommand,-3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_SORTEDSET,ZMSCORE_Keyspecs,1,NULL,2),.args=ZMSCORE_Args},
@@ -11969,8 +12593,8 @@ struct COMMAND_STRUCT redisCommandTable[] = {
{MAKE_CMD("zrevrank","Returns the index of a member in a sorted set ordered by descending scores.","O(log(N))","2.0.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZREVRANK_History,1,ZREVRANK_Tips,0,zrevrankCommand,-3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_SORTEDSET,ZREVRANK_Keyspecs,1,NULL,3),.args=ZREVRANK_Args},
{MAKE_CMD("zscan","Iterates over members and scores of a sorted set.","O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection.","2.8.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZSCAN_History,0,ZSCAN_Tips,1,zscanCommand,-3,CMD_READONLY,ACL_CATEGORY_SORTEDSET,ZSCAN_Keyspecs,1,NULL,4),.args=ZSCAN_Args},
{MAKE_CMD("zscore","Returns the score of a member in a sorted set.","O(1)","1.2.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZSCORE_History,0,ZSCORE_Tips,0,zscoreCommand,3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_SORTEDSET,ZSCORE_Keyspecs,1,NULL,2),.args=ZSCORE_Args},
{MAKE_CMD("zunion","Returns the union of multiple sorted sets.","O(N)+O(M*log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set.","6.2.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZUNION_History,0,ZUNION_Tips,0,zunionCommand,-3,CMD_READONLY,ACL_CATEGORY_SORTEDSET,ZUNION_Keyspecs,1,zunionInterDiffGetKeys,5),.args=ZUNION_Args},
{MAKE_CMD("zunionstore","Stores the union of multiple sorted sets in a key.","O(N)+O(M log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set.","2.0.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZUNIONSTORE_History,0,ZUNIONSTORE_Tips,0,zunionstoreCommand,-4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SORTEDSET,ZUNIONSTORE_Keyspecs,2,zunionInterDiffStoreGetKeys,5),.args=ZUNIONSTORE_Args},
{MAKE_CMD("zunion","Returns the union of multiple sorted sets.","O(N)+O(M*log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set.","6.2.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZUNION_History,1,ZUNION_Tips,0,zunionCommand,-3,CMD_READONLY,ACL_CATEGORY_SORTEDSET,ZUNION_Keyspecs,1,zunionInterDiffGetKeys,5),.args=ZUNION_Args},
{MAKE_CMD("zunionstore","Stores the union of multiple sorted sets in a key.","O(N)+O(M log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set.","2.0.0",CMD_DOC_NONE,NULL,NULL,"sorted_set",COMMAND_GROUP_SORTED_SET,ZUNIONSTORE_History,1,ZUNIONSTORE_Tips,0,zunionstoreCommand,-4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SORTEDSET,ZUNIONSTORE_Keyspecs,2,zunionInterDiffStoreGetKeys,5),.args=ZUNIONSTORE_Args},
/* stream */
{MAKE_CMD("xack","Returns the number of messages that were successfully acknowledged by the consumer group member of a stream.","O(1) for each message ID processed.","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XACK_History,0,XACK_Tips,0,xackCommand,-4,CMD_WRITE|CMD_FAST,ACL_CATEGORY_STREAM,XACK_Keyspecs,1,NULL,3),.args=XACK_Args},
{MAKE_CMD("xackdel","Acknowledges and deletes one or multiple messages for a stream consumer group.","O(1) for each message ID processed.","8.2.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XACKDEL_History,0,XACKDEL_Tips,0,xackdelCommand,-6,CMD_WRITE|CMD_FAST,ACL_CATEGORY_STREAM,XACKDEL_Keyspecs,1,NULL,4),.args=XACKDEL_Args},
@@ -11984,6 +12608,7 @@ struct COMMAND_STRUCT redisCommandTable[] = {
{MAKE_CMD("xidmprecord","An internal command for setting IDMP metadata on an existing stream message.","O(1)","8.6.2",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XIDMPRECORD_History,0,XIDMPRECORD_Tips,0,xidmprecordCommand,5,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_STREAM,XIDMPRECORD_Keyspecs,1,NULL,4),.args=XIDMPRECORD_Args},
{MAKE_CMD("xinfo","A container for stream introspection commands.","Depends on subcommand.","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XINFO_History,0,XINFO_Tips,0,NULL,-2,0,0,XINFO_Keyspecs,0,NULL,0),.subcommands=XINFO_Subcommands},
{MAKE_CMD("xlen","Return the number of messages in a stream.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XLEN_History,0,XLEN_Tips,0,xlenCommand,2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_STREAM,XLEN_Keyspecs,1,NULL,1),.args=XLEN_Args},
{MAKE_CMD("xnack","Releases claimed messages back to the group's PEL without acknowledging them, making them available for re-delivery.","O(1) for each message ID processed.","8.8.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XNACK_History,0,XNACK_Tips,0,xnackCommand,-7,CMD_WRITE|CMD_FAST,ACL_CATEGORY_STREAM,XNACK_Keyspecs,1,NULL,6),.args=XNACK_Args},
{MAKE_CMD("xpending","Returns the information and entries from a stream consumer group's pending entries list.","O(N) with N being the number of elements returned, so asking for a small fixed number of entries per call is O(1). O(M), where M is the total number of entries scanned when used with the IDLE filter. When the command returns just the summary and the list of consumers is small, it runs in O(1) time; otherwise, an additional O(N) time for iterating every consumer.","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XPENDING_History,1,XPENDING_Tips,1,xpendingCommand,-3,CMD_READONLY,ACL_CATEGORY_STREAM,XPENDING_Keyspecs,1,NULL,3),.args=XPENDING_Args},
{MAKE_CMD("xrange","Returns the messages from a stream within a range of IDs.","O(N) with N being the number of elements being returned. If N is constant (e.g. always asking for the first 10 elements with COUNT), you can consider it O(1).","5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XRANGE_History,1,XRANGE_Tips,0,xrangeCommand,-4,CMD_READONLY,ACL_CATEGORY_STREAM,XRANGE_Keyspecs,1,NULL,4),.args=XRANGE_Args},
{MAKE_CMD("xread","Returns messages from multiple streams with IDs greater than the ones requested. Blocks until a message is available otherwise.",NULL,"5.0.0",CMD_DOC_NONE,NULL,NULL,"stream",COMMAND_GROUP_STREAM,XREAD_History,0,XREAD_Tips,0,xreadCommand,-4,CMD_BLOCKING|CMD_READONLY,ACL_CATEGORY_STREAM,XREAD_Keyspecs,1,xreadGetKeys,3),.args=XREAD_Args},
@@ -11997,7 +12622,6 @@ struct COMMAND_STRUCT redisCommandTable[] = {
{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,delexGetKeys,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("gcra","Rate limit via GCRA (Generic Cell Rate Algorithm).","O(1)","8.8.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,GCRA_History,0,GCRA_Tips,0,gcraCommand,-5,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_STRING,GCRA_Keyspecs,1,NULL,5),.args=GCRA_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},
@@ -12006,6 +12630,7 @@ struct COMMAND_STRUCT redisCommandTable[] = {
{MAKE_CMD("incr","Increments 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,INCR_History,0,INCR_Tips,0,incrCommand,2,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_STRING,INCR_Keyspecs,1,NULL,1),.args=INCR_Args},
{MAKE_CMD("incrby","Increments the integer value of a key by a number. 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,INCRBY_History,0,INCRBY_Tips,0,incrbyCommand,3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_STRING,INCRBY_Keyspecs,1,NULL,2),.args=INCRBY_Args},
{MAKE_CMD("incrbyfloat","Increment the floating point value of a key by a number. Uses 0 as initial value if the key doesn't exist.","O(1)","2.6.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,INCRBYFLOAT_History,0,INCRBYFLOAT_Tips,0,incrbyfloatCommand,3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_STRING,INCRBYFLOAT_Keyspecs,1,NULL,2),.args=INCRBYFLOAT_Args},
{MAKE_CMD("increx","Increments the numeric value of a key by a number and sets its expiration time. Uses 0 as initial value if the key doesn't exist.","O(1)","8.8.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,INCREX_History,0,INCREX_Tips,0,increxCommand,-2,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_STRING,INCREX_Keyspecs,1,NULL,7),.args=INCREX_Args},
{MAKE_CMD("lcs","Finds the longest common substring.","O(N*M) where N and M are the lengths of s1 and s2, respectively","7.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,LCS_History,0,LCS_Tips,0,lcsCommand,-3,CMD_READONLY,ACL_CATEGORY_STRING,LCS_Keyspecs,1,NULL,6),.args=LCS_Args},
{MAKE_CMD("mget","Atomically returns the string values of one or more keys.","O(N) where N is the number of keys to retrieve.","1.0.0",CMD_DOC_NONE,NULL,NULL,"string",COMMAND_GROUP_STRING,MGET_History,0,MGET_Tips,1,mgetCommand,-2,CMD_READONLY|CMD_FAST,ACL_CATEGORY_STRING,MGET_Keyspecs,1,NULL,1),.args=MGET_Args},
{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},
+48
View File
@@ -0,0 +1,48 @@
{
"ARCOUNT": {
"summary": "Returns the number of non-empty elements in an array.",
"complexity": "O(1)",
"group": "array",
"since": "8.8.0",
"arity": 2,
"function": "arcountCommand",
"command_flags": [
"READONLY",
"FAST"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RO",
"ACCESS"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"description": "The number of non-empty elements, or 0 if key does not exist.",
"type": "integer"
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
}
]
}
}
+53
View File
@@ -0,0 +1,53 @@
{
"ARDEL": {
"summary": "Deletes elements at the specified indices in an array.",
"complexity": "O(N) where N is the number of indices to delete",
"group": "array",
"since": "8.8.0",
"arity": -3,
"function": "ardelCommand",
"command_flags": [
"WRITE",
"FAST"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RW",
"DELETE"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"description": "Number of elements deleted.",
"type": "integer"
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "index",
"type": "integer",
"multiple": true
}
]
}
}
+62
View File
@@ -0,0 +1,62 @@
{
"ARDELRANGE": {
"summary": "Deletes elements in one or more ranges.",
"complexity": "Proportional to the number of existing elements / slices touched, not to the numeric span of the requested ranges",
"group": "array",
"since": "8.8.0",
"arity": -4,
"function": "ardelrangeCommand",
"command_flags": [
"WRITE"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RW",
"DELETE"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"description": "Number of elements deleted.",
"type": "integer"
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "range",
"type": "block",
"multiple": true,
"arguments": [
{
"name": "start",
"type": "integer"
},
{
"name": "end",
"type": "integer"
}
]
}
]
}
}
+60
View File
@@ -0,0 +1,60 @@
{
"ARGET": {
"summary": "Gets the value at an index in an array.",
"complexity": "O(1)",
"group": "array",
"since": "8.8.0",
"arity": 3,
"function": "argetCommand",
"command_flags": [
"READONLY",
"FAST"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RO",
"ACCESS"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"oneOf": [
{
"description": "The value at the given index.",
"type": "string"
},
{
"description": "Null reply if key or index does not exist.",
"type": "null"
}
]
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "index",
"type": "integer"
}
]
}
}
+64
View File
@@ -0,0 +1,64 @@
{
"ARGETRANGE": {
"summary": "Gets values in a range of indices.",
"complexity": "O(N) where N is the range length",
"group": "array",
"since": "8.8.0",
"arity": 4,
"function": "argetrangeCommand",
"command_flags": [
"READONLY"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RO",
"ACCESS"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"type": "array",
"items": {
"oneOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "start",
"type": "integer"
},
{
"name": "end",
"type": "integer"
}
]
}
}
+182
View File
@@ -0,0 +1,182 @@
{
"ARGREP": {
"summary": "Searches array elements in a range using textual predicates.",
"complexity": "O(P * C) where P is the number of visited positions in touched slices and C is the cost of evaluating the predicates on one existing element.",
"group": "array",
"since": "8.8.0",
"arity": -6,
"function": "argrepCommand",
"command_flags": [
"READONLY"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RO",
"ACCESS"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"anyOf": [
{
"description": "Array of matching indexes.",
"type": "array",
"items": {
"type": "integer",
"description": "Index of a matching element"
}
},
{
"description": "Array of [index, value] pairs. Returned in case `WITHVALUES` was used.",
"type": "array",
"items": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": [
{
"type": "integer",
"description": "Index of a matching element"
},
{
"type": "string",
"description": "Value at that index"
}
]
}
}
]
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "start",
"type": "string"
},
{
"name": "end",
"type": "string"
},
{
"name": "predicate",
"type": "oneof",
"multiple": true,
"arguments": [
{
"name": "exact",
"type": "block",
"arguments": [
{
"name": "exact",
"type": "pure-token",
"token": "EXACT"
},
{
"name": "string",
"type": "string"
}
]
},
{
"name": "match",
"type": "block",
"arguments": [
{
"name": "match",
"type": "pure-token",
"token": "MATCH"
},
{
"name": "string",
"type": "string"
}
]
},
{
"name": "glob",
"type": "block",
"arguments": [
{
"name": "glob",
"type": "pure-token",
"token": "GLOB"
},
{
"name": "pattern",
"type": "string"
}
]
},
{
"name": "re",
"type": "block",
"arguments": [
{
"name": "re",
"type": "pure-token",
"token": "RE"
},
{
"name": "pattern",
"type": "string"
}
]
}
]
},
{
"name": "options",
"type": "oneof",
"optional": true,
"multiple": true,
"arguments": [
{
"name": "and",
"type": "pure-token",
"token": "AND"
},
{
"name": "or",
"type": "pure-token",
"token": "OR"
},
{
"name": "limit",
"type": "integer",
"token": "LIMIT"
},
{
"name": "withvalues",
"type": "pure-token",
"token": "WITHVALUES"
},
{
"name": "nocase",
"type": "pure-token",
"token": "NOCASE"
}
]
}
]
}
}
+103
View File
@@ -0,0 +1,103 @@
{
"ARINFO": {
"summary": "Returns metadata about an array.",
"complexity": "O(1), or O(N) with FULL option where N is the number of slices.",
"group": "array",
"since": "8.8.0",
"arity": -2,
"function": "arinfoCommand",
"command_flags": [
"READONLY"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RO",
"ACCESS"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"type": "object",
"additionalProperties": false,
"properties": {
"count": {
"type": "integer",
"description": "Total number of non-empty elements."
},
"len": {
"type": "integer",
"description": "Logical length (highest index + 1)."
},
"next-insert-index": {
"type": "integer",
"description": "Index the next ARINSERT would use, or 0 if unset/exhausted."
},
"slices": {
"type": "integer",
"description": "Number of allocated slices."
},
"directory-size": {
"type": "integer",
"description": "Directory allocation capacity (flat dir_alloc or superdir sdir_cap)."
},
"super-dir-entries": {
"type": "integer",
"description": "Number of super-directory entries (0 if not in superdir mode)."
},
"slice-size": {
"type": "integer",
"description": "Configured slice size."
},
"dense-slices": {
"type": "integer",
"description": "Number of dense slices (FULL only)."
},
"sparse-slices": {
"type": "integer",
"description": "Number of sparse slices (FULL only)."
},
"avg-dense-size": {
"type": "number",
"description": "Average allocation size of dense slices (FULL only)."
},
"avg-dense-fill": {
"type": "number",
"description": "Average fill rate of dense slices (FULL only)."
},
"avg-sparse-size": {
"type": "number",
"description": "Average capacity of sparse slices (FULL only)."
}
}
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "full",
"type": "pure-token",
"token": "FULL",
"optional": true
}
]
}
}
+54
View File
@@ -0,0 +1,54 @@
{
"ARINSERT": {
"summary": "Inserts one or more values at consecutive indices.",
"complexity": "O(N) where N is the number of values",
"group": "array",
"since": "8.8.0",
"arity": -3,
"function": "arinsertCommand",
"command_flags": [
"WRITE",
"DENYOOM",
"FAST"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RW",
"UPDATE"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"description": "The last index where a value was inserted.",
"type": "integer"
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "value",
"type": "string",
"multiple": true
}
]
}
}
+66
View File
@@ -0,0 +1,66 @@
{
"ARLASTITEMS": {
"summary": "Returns the most recently inserted elements.",
"complexity": "O(N) where N is the count",
"group": "array",
"since": "8.8.0",
"arity": -3,
"function": "arlastitemsCommand",
"command_flags": [
"READONLY"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RO",
"ACCESS"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"type": "array",
"items": {
"oneOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "count",
"type": "integer"
},
{
"name": "rev",
"type": "pure-token",
"token": "REV",
"optional": true
}
]
}
}
+48
View File
@@ -0,0 +1,48 @@
{
"ARLEN": {
"summary": "Returns the length of an array (max index + 1).",
"complexity": "O(1)",
"group": "array",
"since": "8.8.0",
"arity": 2,
"function": "arlenCommand",
"command_flags": [
"READONLY",
"FAST"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RO",
"ACCESS"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"description": "The length of the array (max index + 1), or 0 if key does not exist.",
"type": "integer"
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
}
]
}
}
+62
View File
@@ -0,0 +1,62 @@
{
"ARMGET": {
"summary": "Gets values at multiple indices in an array.",
"complexity": "O(N) where N is the number of indices",
"group": "array",
"since": "8.8.0",
"arity": -3,
"function": "armgetCommand",
"command_flags": [
"READONLY",
"FAST"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RO",
"ACCESS"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"type": "array",
"items": {
"oneOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "index",
"type": "integer",
"multiple": true
}
]
}
}
+64
View File
@@ -0,0 +1,64 @@
{
"ARMSET": {
"summary": "Sets multiple index-value pairs in an array.",
"complexity": "O(N) where N is the number of pairs",
"group": "array",
"since": "8.8.0",
"arity": -4,
"function": "armsetCommand",
"command_flags": [
"WRITE",
"DENYOOM",
"FAST"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RW",
"UPDATE"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"description": "Number of new slots that were set (previously empty).",
"type": "integer"
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "data",
"type": "block",
"multiple": true,
"arguments": [
{
"name": "index",
"type": "integer"
},
{
"name": "value",
"type": "string"
}
]
}
]
}
}
+56
View File
@@ -0,0 +1,56 @@
{
"ARNEXT": {
"summary": "Returns the next index ARINSERT would use.",
"complexity": "O(1)",
"group": "array",
"since": "8.8.0",
"arity": 2,
"function": "arnextCommand",
"command_flags": [
"READONLY",
"FAST"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RO",
"ACCESS"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"oneOf": [
{
"description": "The next index ARINSERT would use. Returns 0 for missing keys or when no insert happened yet.",
"type": "integer"
},
{
"description": "Null when the insertion cursor is exhausted (next insert would overflow).",
"type": "null"
}
]
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
}
]
}
}
+123
View File
@@ -0,0 +1,123 @@
{
"AROP": {
"summary": "Performs aggregate operations on array elements in a range.",
"complexity": "O(P) where P is visited positions in touched slices (dense scanned slots + sparse entries), with worst-case O(|end-start|+1) and typical case close to O(N), where N is the number of existing elements in range.",
"group": "array",
"since": "8.8.0",
"arity": -5,
"function": "aropCommand",
"command_flags": [
"READONLY"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RO",
"ACCESS"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"oneOf": [
{
"description": "Result of the operation.",
"type": "string"
},
{
"description": "Integer result for MATCH, USED, AND, OR, XOR.",
"type": "integer"
},
{
"description": "Null if no elements match the operation.",
"type": "null"
}
]
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "start",
"type": "integer"
},
{
"name": "end",
"type": "integer"
},
{
"name": "operation",
"type": "oneof",
"arguments": [
{
"name": "sum",
"type": "pure-token",
"token": "SUM"
},
{
"name": "min",
"type": "pure-token",
"token": "MIN"
},
{
"name": "max",
"type": "pure-token",
"token": "MAX"
},
{
"name": "and",
"type": "pure-token",
"token": "AND"
},
{
"name": "or",
"type": "pure-token",
"token": "OR"
},
{
"name": "xor",
"type": "pure-token",
"token": "XOR"
},
{
"name": "match",
"type": "block",
"arguments": [
{
"name": "match",
"type": "pure-token",
"token": "MATCH"
},
{
"name": "value",
"type": "string"
}
]
},
{
"name": "used",
"type": "pure-token",
"token": "USED"
}
]
}
]
}
}
+57
View File
@@ -0,0 +1,57 @@
{
"ARRING": {
"summary": "Inserts values into a ring buffer of specified size, wrapping and truncating as needed.",
"complexity": "O(M) normally, O(N+M) on ring resize, where N is the maximum of the old and new ring size and M is the number of inserted values",
"group": "array",
"since": "8.8.0",
"arity": -4,
"function": "arringCommand",
"command_flags": [
"WRITE",
"DENYOOM"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RW",
"UPDATE"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"description": "The last index where a value was inserted.",
"type": "integer"
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "size",
"type": "integer"
},
{
"name": "value",
"type": "string",
"multiple": true
}
]
}
}
+76
View File
@@ -0,0 +1,76 @@
{
"ARSCAN": {
"summary": "Iterates existing elements in a range, returning index-value pairs.",
"complexity": "O(P) where P is visited positions in touched slices (dense scanned slots + sparse entries), with worst-case O(|end-start|+1) and typical case close to O(N), where N is the number of existing elements in range.",
"group": "array",
"since": "8.8.0",
"arity": -4,
"function": "arscanCommand",
"command_flags": [
"READONLY"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RO",
"ACCESS"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"description": "Array of [index, value] pairs.",
"type": "array",
"items": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": [
{
"type": "integer",
"description": "Index of existing element"
},
{
"type": "string",
"description": "Value at that index"
}
]
}
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "start",
"type": "integer"
},
{
"name": "end",
"type": "integer"
},
{
"name": "limit",
"token": "LIMIT",
"type": "integer",
"optional": true
}
]
}
}
+52
View File
@@ -0,0 +1,52 @@
{
"ARSEEK": {
"summary": "Sets the ARINSERT / ARRING cursor to a specific index.",
"complexity": "O(1)",
"group": "array",
"since": "8.8.0",
"arity": 3,
"function": "arseekCommand",
"command_flags": [
"WRITE",
"FAST"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RW",
"UPDATE"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"description": "1 if the cursor was set, 0 if the key does not exist.",
"type": "integer"
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "index",
"type": "integer"
}
]
}
}
+58
View File
@@ -0,0 +1,58 @@
{
"ARSET": {
"summary": "Sets one or more contiguous values starting at an index in an array.",
"complexity": "O(N) where N is the number of values",
"group": "array",
"since": "8.8.0",
"arity": -4,
"function": "arsetCommand",
"command_flags": [
"WRITE",
"DENYOOM",
"FAST"
],
"acl_categories": [
"ARRAY"
],
"key_specs": [
{
"flags": [
"RW",
"UPDATE"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"description": "Number of new slots that were set (previously empty).",
"type": "integer"
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "index",
"type": "integer"
},
{
"name": "value",
"type": "string",
"multiple": true
}
]
}
}
+6
View File
@@ -59,6 +59,9 @@
{
"const": "hyperloglog"
},
{
"const": "array"
},
{
"const": "list"
},
@@ -91,6 +94,9 @@
},
{
"const": "transactions"
},
{
"const": "rate_limit"
}
]
},
-92
View File
@@ -1,92 +0,0 @@
{
"GCRA": {
"summary": "Rate limit via GCRA (Generic Cell Rate Algorithm).",
"complexity": "O(1)",
"group": "string",
"since": "8.8.0",
"arity": -5,
"function": "gcraCommand",
"command_flags": [
"WRITE",
"DENYOOM",
"FAST"
],
"acl_categories": [
"STRING"
],
"key_specs": [
{
"flags": [
"RW",
"ACCESS",
"UPDATE"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"type": "array",
"minItems": 5,
"maxItems": 5,
"description": "Rate limiting result",
"items": [
{
"type": "integer",
"description": "Limited: 0 if allowed, 1 if rate limited"
},
{
"type": "integer",
"description": "Max request number: always equal to max_burst+1"
},
{
"type": "integer",
"description": "Number of requests available immediately"
},
{
"type": "integer",
"description": "Retry after: seconds after which caller should retry. Always -1 if not limited"
},
{
"type": "integer",
"description": "Full burst after: seconds after which a full burst will be allowed"
}
]
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "max-burst",
"type": "integer"
},
{
"name": "requests-per-period",
"type": "integer"
},
{
"name": "period",
"type": "double"
},
{
"name": "count",
"type": "integer",
"token": "NUM_REQUESTS",
"optional": true
}
]
}
}
+138
View File
@@ -0,0 +1,138 @@
{
"INCREX": {
"summary": "Increments the numeric value of a key by a number and sets its expiration time. Uses 0 as initial value if the key doesn't exist.",
"complexity": "O(1)",
"group": "string",
"since": "8.8.0",
"arity": -2,
"function": "increxCommand",
"command_flags": [
"WRITE",
"DENYOOM",
"FAST"
],
"acl_categories": [
"STRING"
],
"key_specs": [
{
"flags": [
"RW",
"ACCESS",
"UPDATE"
],
"begin_search": {
"index": {
"pos": 1
}
},
"find_keys": {
"range": {
"lastkey": 0,
"step": 1,
"limit": 0
}
}
}
],
"reply_schema": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": [
{
"description": "the value of the key after the increment",
"type": "number"
},
{
"description": "the actual increment",
"type": "number"
}
]
},
"arguments": [
{
"name": "key",
"type": "key",
"key_spec_index": 0
},
{
"name": "increment",
"type": "oneof",
"optional": true,
"arguments": [
{
"name": "float",
"type": "double",
"token": "BYFLOAT"
},
{
"name": "integer",
"type": "integer",
"token": "BYINT"
}
]
},
{
"name": "saturate",
"token": "SATURATE",
"type": "pure-token",
"optional": true,
"summary": "Saturate the result to LBOUND/UBOUND (or the type limits when no explicit bound is given) when out of bounds. Without this option, out-of-bounds operations are rejected and reply [current_value, 0]."
},
{
"name": "lowerbound",
"token": "LBOUND",
"type": "string",
"summary": "Integer when used with BYINT, floating-point when used with BYFLOAT.",
"optional": true
},
{
"name": "upperbound",
"token": "UBOUND",
"type": "string",
"summary": "Integer when used with BYINT, floating-point when used with BYFLOAT.",
"optional": true
},
{
"name": "expiration",
"type": "oneof",
"optional": true,
"arguments": [
{
"name": "seconds",
"type": "integer",
"token": "EX"
},
{
"name": "milliseconds",
"type": "integer",
"token": "PX"
},
{
"name": "unix-time-seconds",
"type": "unix-time",
"token": "EXAT"
},
{
"name": "unix-time-milliseconds",
"type": "unix-time",
"token": "PXAT"
},
{
"name": "persist",
"type": "pure-token",
"token": "PERSIST"
}
]
},
{
"name": "enx",
"type": "pure-token",
"token": "ENX",
"optional": true,
"summary": "Only set the expiration if the key currently has no TTL. Requires one of EX/PX/EXAT/PXAT; cannot be combined with PERSIST."
}
]
}
}
+6
View File
@@ -38,6 +38,12 @@
"clients.normal": {
"type": "integer"
},
"clients.normal.shared": {
"type": "integer"
},
"clients.normal.unshared": {
"type": "integer"
},
"cluster.links": {
"type": "integer"
},
+1
View File
@@ -6,6 +6,7 @@
"since": "2.8.9",
"arity": -2,
"function": "pfmergeCommand",
"get_keys_function": "pfmergeGetKeys",
"command_flags": [
"WRITE",
"DENYOOM"
+2 -1
View File
@@ -11,7 +11,8 @@
"NOSCRIPT",
"LOADING",
"STALE",
"SENTINEL"
"SENTINEL",
"DENYOOM"
],
"arguments": [
{
+2 -1
View File
@@ -10,7 +10,8 @@
"PUBSUB",
"NOSCRIPT",
"LOADING",
"STALE"
"STALE",
"DENYOOM"
],
"arguments": [
{

Some files were not shown because too many files have changed in this diff Show More