merge: upstream/unstable (preserve Hanzo brand, 117 commits)
This commit is contained in:
+15
-12
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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 }}
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
Vendored
+7
-6
@@ -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)
|
||||
|
||||
Vendored
-27
@@ -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
|
||||
Vendored
-21
@@ -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
|
||||
Vendored
-3838
File diff suppressed because it is too large
Load Diff
-32
@@ -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;
|
||||
}
|
||||
Vendored
-15
@@ -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__ */
|
||||
Vendored
+29
@@ -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.
|
||||
Vendored
+79
@@ -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)
|
||||
Vendored
+276
@@ -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.
|
||||
Vendored
+188
@@ -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 */
|
||||
Vendored
+86
@@ -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 */
|
||||
Vendored
+584
@@ -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(¶ms);
|
||||
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 */
|
||||
Vendored
+226
@@ -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 */
|
||||
Vendored
+128
@@ -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 */
|
||||
Vendored
+2673
File diff suppressed because it is too large
Load Diff
Vendored
+27
@@ -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 */
|
||||
Vendored
+73
@@ -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;
|
||||
}
|
||||
Vendored
+19
@@ -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);
|
||||
Vendored
+319
@@ -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 */
|
||||
Vendored
+676
@@ -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;
|
||||
}
|
||||
Vendored
+538
@@ -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 */
|
||||
Vendored
+215
@@ -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. */
|
||||
}
|
||||
Vendored
+155
@@ -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 */
|
||||
Vendored
+66
@@ -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 */
|
||||
Vendored
+1758
File diff suppressed because it is too large
Load Diff
Vendored
+52
@@ -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 */
|
||||
Vendored
+123
@@ -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 */
|
||||
Vendored
+76
@@ -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 */
|
||||
Vendored
+362
@@ -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 */
|
||||
Vendored
+77
@@ -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 */
|
||||
Vendored
+48
@@ -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 */
|
||||
Vendored
+14
@@ -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
|
||||
Vendored
+344
@@ -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 */
|
||||
Vendored
+1871
File diff suppressed because it is too large
Load Diff
Vendored
+303
@@ -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;
|
||||
}
|
||||
Vendored
+85
@@ -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;
|
||||
}
|
||||
Vendored
+192
@@ -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
@@ -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,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
|
||||
|
||||
|
||||
@@ -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,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,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
|
||||
|
||||
|
||||
@@ -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}")
|
||||
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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=""
|
||||
|
||||
|
||||
@@ -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=""
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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. */
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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))
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,9 @@
|
||||
{
|
||||
"const": "hyperloglog"
|
||||
},
|
||||
{
|
||||
"const": "array"
|
||||
},
|
||||
{
|
||||
"const": "list"
|
||||
},
|
||||
@@ -91,6 +94,9 @@
|
||||
},
|
||||
{
|
||||
"const": "transactions"
|
||||
},
|
||||
{
|
||||
"const": "rate_limit"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,12 @@
|
||||
"clients.normal": {
|
||||
"type": "integer"
|
||||
},
|
||||
"clients.normal.shared": {
|
||||
"type": "integer"
|
||||
},
|
||||
"clients.normal.unshared": {
|
||||
"type": "integer"
|
||||
},
|
||||
"cluster.links": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"since": "2.8.9",
|
||||
"arity": -2,
|
||||
"function": "pfmergeCommand",
|
||||
"get_keys_function": "pfmergeGetKeys",
|
||||
"command_flags": [
|
||||
"WRITE",
|
||||
"DENYOOM"
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
"NOSCRIPT",
|
||||
"LOADING",
|
||||
"STALE",
|
||||
"SENTINEL"
|
||||
"SENTINEL",
|
||||
"DENYOOM"
|
||||
],
|
||||
"arguments": [
|
||||
{
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user