mirror of
https://github.com/redis/redis.git
synced 2026-05-28 04:02:46 -04:00
Merge branch 'unstable' into stream-load-integrity
This commit is contained in:
commit
084b7af633
114 changed files with 23532 additions and 453 deletions
165
.github/workflows/post-release-automation.yml
vendored
165
.github/workflows/post-release-automation.yml
vendored
|
|
@ -1,165 +0,0 @@
|
|||
name: Post-Release Automation
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
extract-release-info:
|
||||
if: github.repository == 'redis/redis'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
tag_name: ${{ steps.release-info.outputs.tag_name }}
|
||||
release_type: ${{ steps.release-info.outputs.release_type }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Extract and validate release information
|
||||
id: release-info
|
||||
env:
|
||||
TAG_NAME: ${{ github.event.release.tag_name }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
echo "tag_name=${TAG_NAME}" >> $GITHUB_OUTPUT
|
||||
echo "Release tag: ${TAG_NAME}"
|
||||
|
||||
LATEST_TAG=$(gh release view --json tagName --jq '.tagName')
|
||||
echo "Latest release tag(from gh release): ${LATEST_TAG}"
|
||||
|
||||
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
|
||||
|
||||
create-tarball:
|
||||
needs: extract-release-info
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TAG_NAME: ${{ needs.extract-release-info.outputs.tag_name }}
|
||||
outputs:
|
||||
sha256: ${{ steps.checksum.outputs.sha256 }}
|
||||
size_mb: ${{ steps.size.outputs.size_mb }}
|
||||
size_warning: ${{ steps.size.outputs.size_warning }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ env.TAG_NAME }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Create tarball
|
||||
run: ./utils/releasetools/01_create_tarball.sh "$TAG_NAME"
|
||||
|
||||
- name: Verify tarball size
|
||||
id: size
|
||||
run: |
|
||||
TARBALL="/tmp/redis-${TAG_NAME}.tar.gz"
|
||||
SIZE_MB=$(du -m "$TARBALL" | cut -f1)
|
||||
echo "Tarball size: ${SIZE_MB} MB"
|
||||
echo "size_mb=${SIZE_MB}" >> $GITHUB_OUTPUT
|
||||
if [ "$SIZE_MB" -lt 3 ] || [ "$SIZE_MB" -gt 5 ]; then
|
||||
echo "::warning::Tarball size ${SIZE_MB} MB is outside expected range (3-5 MB)"
|
||||
echo "size_warning=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "size_warning=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Calculate SHA256 checksum
|
||||
id: checksum
|
||||
run: |
|
||||
TARBALL="/tmp/redis-${TAG_NAME}.tar.gz"
|
||||
SHA256=$(shasum -a 256 "$TARBALL" | cut -d' ' -f1)
|
||||
echo "SHA256: $SHA256"
|
||||
echo "sha256=$SHA256" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Upload tarball as artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: redis-${{ env.TAG_NAME }}-tarball
|
||||
path: /tmp/redis-${{ env.TAG_NAME }}.tar.gz
|
||||
compression-level: 0
|
||||
|
||||
# approval-gate:
|
||||
# needs: [extract-release-info, create-tarball]
|
||||
# if: needs.extract-release-info.outputs.release_type == 'latest'
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - name: Approval gate
|
||||
# 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
|
||||
|
||||
# upload-tarball:
|
||||
# needs: [extract-release-info, create-tarball, approval-gate]
|
||||
# if: always() && !cancelled() && needs.create-tarball.result == 'success' && (needs.approval-gate.result == 'success' || needs.approval-gate.result == 'skipped')
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - name: Upload tarball
|
||||
# run: |
|
||||
# echo "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
|
||||
|
||||
# test-release-tarball:
|
||||
# needs: upload-tarball
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - name: Test release tarball
|
||||
# run: |
|
||||
# echo "TODO: Implement release testing using utils/releasetools/03_test_release.sh"
|
||||
# # This will:
|
||||
# # - Download the uploaded tarball
|
||||
# # - Extract and build Redis
|
||||
|
||||
# update-release-hashes:
|
||||
# needs: test-release-tarball
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - name: Update release hashes
|
||||
# run: |
|
||||
# echo "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
|
||||
|
||||
summary-and-notify:
|
||||
needs: [extract-release-info, create-tarball] # update-release-hashes
|
||||
if: always() && github.repository == 'redis/redis'
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TAG_NAME: ${{ needs.extract-release-info.outputs.tag_name }}
|
||||
RELEASE_TYPE: ${{ needs.extract-release-info.outputs.release_type }}
|
||||
SHA256: ${{ needs.create-tarball.outputs.sha256 }}
|
||||
SIZE_MB: ${{ needs.create-tarball.outputs.size_mb }}
|
||||
SIZE_WARNING: ${{ needs.create-tarball.outputs.size_warning }}
|
||||
steps:
|
||||
- name: Summary
|
||||
run: |
|
||||
{
|
||||
echo "## Post-Release Automation Summary"
|
||||
echo ""
|
||||
echo "- **Release Tag:** ${TAG_NAME}"
|
||||
echo "- **Release Type:** ${RELEASE_TYPE}"
|
||||
echo "- **Tarball SHA256:** ${SHA256}"
|
||||
echo "- **Tarball Size:** ${SIZE_MB} MB"
|
||||
if [ "${SIZE_WARNING}" == "true" ]; then
|
||||
echo ""
|
||||
echo "> [!WARNING]"
|
||||
echo "> Tarball size is outside expected range, check the logs for details."
|
||||
fi
|
||||
} >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# - name: Send Slack notification
|
||||
# run: |
|
||||
# echo "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
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -30,6 +30,7 @@ deps/lua/src/luac
|
|||
deps/lua/src/liblua.a
|
||||
deps/hdr_histogram/libhdrhistogram.a
|
||||
deps/fpconv/libfpconv.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.
|
||||
|
|
|
|||
8
deps/Makefile
vendored
8
deps/Makefile
vendored
|
|
@ -59,6 +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 tre && $(MAKE) clean) > /dev/null || true
|
||||
-(cd xxhash && $(MAKE) clean) > /dev/null || true
|
||||
-(rm -f .make-*)
|
||||
|
||||
|
|
@ -94,6 +95,13 @@ fpconv: .make-prerequisites
|
|||
|
||||
.PHONY: fpconv
|
||||
|
||||
tre: .make-prerequisites
|
||||
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
|
||||
cd tre && $(MAKE) CFLAGS="$(DEPS_CFLAGS)" LDFLAGS="$(DEPS_LDFLAGS)"
|
||||
|
||||
.PHONY: tre
|
||||
|
||||
|
||||
XXHASH_CFLAGS = -fPIC $(DEPS_CFLAGS)
|
||||
xxhash: .make-prerequisites
|
||||
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
|
||||
|
|
|
|||
29
deps/tre/LICENSE
vendored
Normal file
29
deps/tre/LICENSE
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
This is the license, copyright notice, and disclaimer for TRE, a regex
|
||||
matching package (library and tools) with support for approximate
|
||||
matching.
|
||||
|
||||
Copyright (c) 2001-2009 Ville Laurikari <vl@iki.fi>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
79
deps/tre/Makefile
vendored
Normal file
79
deps/tre/Makefile
vendored
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
STD= -std=c99
|
||||
WARN= -Wall
|
||||
OPT= -Os
|
||||
|
||||
ifeq ($(SANITIZER),address)
|
||||
CFLAGS+=-fsanitize=address -fno-sanitize-recover=all -fno-omit-frame-pointer
|
||||
LDFLAGS+=-fsanitize=address
|
||||
else
|
||||
ifeq ($(SANITIZER),undefined)
|
||||
CFLAGS+=-fsanitize=undefined -fno-sanitize-recover=all -fno-omit-frame-pointer
|
||||
LDFLAGS+=-fsanitize=undefined
|
||||
else
|
||||
ifeq ($(SANITIZER),thread)
|
||||
CFLAGS+=-fsanitize=thread -fno-sanitize-recover=all -fno-omit-frame-pointer
|
||||
LDFLAGS+=-fsanitize=thread
|
||||
else
|
||||
ifeq ($(SANITIZER),memory)
|
||||
CFLAGS+=-fsanitize=memory -fsanitize-memory-track-origins=2 -fno-sanitize-recover=all -fno-omit-frame-pointer
|
||||
LDFLAGS+=-fsanitize=memory
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
R_CFLAGS= $(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) -DTRE_REGEX_T_FIELD=value -Ilocal_includes -Ilib
|
||||
R_LDFLAGS= $(LDFLAGS)
|
||||
DEBUG= -g
|
||||
|
||||
R_CC=$(CC) $(R_CFLAGS)
|
||||
R_LD=$(CC) $(R_LDFLAGS)
|
||||
|
||||
AR= ar
|
||||
ARFLAGS= rcs
|
||||
|
||||
TRE_OBJ=lib/regcomp.o lib/regerror.o lib/regexec.o lib/tre-ast.o lib/tre-compile.o \
|
||||
lib/tre-filter.o lib/tre-match-backtrack.o lib/tre-match-parallel.o \
|
||||
lib/tre-mem.o lib/tre-parse.o lib/tre-stack.o lib/xmalloc.o
|
||||
TRE_TESTS=tests/retest tests/test-str-source tests/test-literal-opt tests/test-malformed-regn
|
||||
|
||||
libtre.a: $(TRE_OBJ)
|
||||
$(AR) $(ARFLAGS) $@ $+
|
||||
|
||||
check: $(TRE_TESTS)
|
||||
@set -e; \
|
||||
for test in $(TRE_TESTS); do \
|
||||
echo "TEST $$test"; \
|
||||
./$$test; \
|
||||
done
|
||||
|
||||
tests/retest: tests/retest.c libtre.a
|
||||
$(R_LD) $(R_CFLAGS) -DHAVE_REGNEXEC -DHAVE_REGNCOMP -o $@ $< libtre.a
|
||||
|
||||
tests/test-str-source: tests/test-str-source.c libtre.a
|
||||
$(R_LD) $(R_CFLAGS) -o $@ $< libtre.a
|
||||
|
||||
tests/test-literal-opt: tests/test-literal-opt.c libtre.a
|
||||
$(R_LD) $(R_CFLAGS) -o $@ $< libtre.a
|
||||
|
||||
tests/test-malformed-regn: tests/test-malformed-regn.c libtre.a
|
||||
$(R_LD) $(R_CFLAGS) -o $@ $< libtre.a
|
||||
|
||||
lib/regcomp.o: lib/regcomp.c local_includes/tre.h local_includes/tre-config.h lib/tre-internal.h lib/xmalloc.h
|
||||
lib/regerror.o: lib/regerror.c local_includes/tre.h
|
||||
lib/regexec.o: lib/regexec.c local_includes/tre.h lib/tre-internal.h lib/xmalloc.h
|
||||
lib/tre-ast.o: lib/tre-ast.c lib/tre-ast.h lib/tre-internal.h
|
||||
lib/tre-compile.o: lib/tre-compile.c lib/tre-compile.h lib/tre-internal.h lib/tre-mem.h lib/tre-parse.h lib/tre-stack.h lib/xmalloc.h
|
||||
lib/tre-filter.o: lib/tre-filter.c lib/tre-filter.h lib/tre-internal.h
|
||||
lib/tre-match-backtrack.o: lib/tre-match-backtrack.c lib/tre-internal.h lib/tre-match-utils.h lib/tre-mem.h lib/tre-stack.h
|
||||
lib/tre-match-parallel.o: lib/tre-match-parallel.c lib/tre-internal.h lib/tre-match-utils.h lib/tre-mem.h
|
||||
lib/tre-mem.o: lib/tre-mem.c lib/tre-mem.h
|
||||
lib/tre-parse.o: lib/tre-parse.c lib/tre-ast.h lib/tre-compile.h lib/tre-filter.h lib/tre-internal.h lib/tre-mem.h lib/tre-parse.h lib/tre-stack.h lib/xmalloc.h
|
||||
lib/tre-stack.o: lib/tre-stack.c lib/tre-internal.h lib/tre-stack.h
|
||||
lib/xmalloc.o: lib/xmalloc.c lib/xmalloc.h
|
||||
|
||||
.c.o:
|
||||
$(R_CC) -c -o $@ $<
|
||||
|
||||
clean:
|
||||
rm -f $(TRE_OBJ) libtre.a $(TRE_TESTS)
|
||||
276
deps/tre/README.md
vendored
Normal file
276
deps/tre/README.md
vendored
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
Introduction
|
||||
============
|
||||
|
||||
TRE is a lightweight, robust, and efficient POSIX compliant regexp
|
||||
matching library with some exciting features such as approximate
|
||||
(fuzzy) matching.
|
||||
|
||||
The matching algorithm used in TRE uses linear worst-case time in
|
||||
the length of the text being searched, and quadratic worst-case
|
||||
time in the length of the used regular expression.
|
||||
|
||||
In other words, the time complexity of the algorithm is O(M^2N), where
|
||||
M is the length of the regular expression and N is the length of the
|
||||
text. The used space is also quadratic on the length of the regex,
|
||||
but does not depend on the searched string. This quadratic behaviour
|
||||
occurs only on pathological cases which are probably very rare in
|
||||
practice.
|
||||
|
||||
|
||||
Hacking
|
||||
=======
|
||||
|
||||
Here's how to work with this code.
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
You will need the following tools installed on your system:
|
||||
|
||||
- autoconf
|
||||
- automake
|
||||
- gettext (including autopoint)
|
||||
- libtool
|
||||
- zip (optional)
|
||||
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
First, prepare the tree. Change to the root of the source directory
|
||||
and run
|
||||
|
||||
./utils/autogen.sh
|
||||
|
||||
This will regenerate various things using the prerequisite tools so
|
||||
that you end up with a buildable tree.
|
||||
|
||||
After this, you can run the configure script and build TRE as usual:
|
||||
|
||||
./configure
|
||||
make
|
||||
make check
|
||||
make install
|
||||
|
||||
|
||||
Building a source code package
|
||||
------------------------------
|
||||
|
||||
In a prepared tree, this command creates a source code tarball:
|
||||
|
||||
./configure && make dist
|
||||
|
||||
Alternatively, you can run
|
||||
|
||||
./utils/build-sources.sh
|
||||
|
||||
which builds the source code packages and puts them in the `dist`
|
||||
subdirectory. This script needs a working `zip` command.
|
||||
|
||||
|
||||
Features
|
||||
========
|
||||
|
||||
TRE is not just yet another regexp matcher. TRE has some features
|
||||
which are not there in most free POSIX compatible implementations.
|
||||
Most of these features are not present in non-free implementations
|
||||
either, for that matter.
|
||||
|
||||
Approximate matching
|
||||
--------------------
|
||||
|
||||
Approximate pattern matching allows matches to be approximate, that
|
||||
is, allows the matches to be close to the searched pattern under some
|
||||
measure of closeness. TRE uses the edit-distance measure (also known
|
||||
as the Levenshtein distance) where characters can be inserted,
|
||||
deleted, or substituted in the searched text in order to get an exact
|
||||
match.
|
||||
|
||||
Each insertion, deletion, or substitution adds the distance, or cost,
|
||||
of the match. TRE can report the matches which have a cost lower than
|
||||
some given threshold value. TRE can also be used to search for
|
||||
matches with the lowest cost.
|
||||
|
||||
TRE includes a version of the agrep (approximate grep) command line
|
||||
tool for approximate regexp matching in the style of grep. Unlike
|
||||
other agrep implementations (like the one by Sun Wu and Udi Manber
|
||||
from University of Arizona) TRE agrep allows full regexps of any
|
||||
length, any number of errors, and non-uniform costs for insertion,
|
||||
deletion and substitution.
|
||||
|
||||
Strict standard conformance
|
||||
---------------------------
|
||||
|
||||
POSIX defines the behaviour of regexp functions precisely. TRE
|
||||
attempts to conform to these specifications as strictly as possible.
|
||||
TRE always returns the correct matches for subpatterns, for example.
|
||||
Very few other implementations do this correctly. In fact, the only
|
||||
other implementations besides TRE that I am aware of (free or not)
|
||||
that get it right are Rx by Tom Lord, Regex++ by John Maddock, and the
|
||||
AT&T ast regex by Glenn Fowler and Doug McIlroy.
|
||||
|
||||
The standard TRE tries to conform to is the IEEE Std 1003.1-2001, or
|
||||
Open Group Base Specifications Issue 6, commonly referred to as
|
||||
“POSIX”. The relevant parts are the base specifications on regular
|
||||
expressions (and the rationale) and the description of the `regcomp()`
|
||||
API.
|
||||
|
||||
For an excellent survey on POSIX regexp matchers, see the testregex
|
||||
pages by Glenn Fowler of AT&T Labs Research.
|
||||
|
||||
Predictable matching speed
|
||||
--------------------------
|
||||
|
||||
Because of the matching algorithm used in TRE, the maximum time
|
||||
consumed by any `regexec()` call is always directly proportional to
|
||||
the length of the searched string. There is one exception: if back
|
||||
references are used, the matching may take time that grows
|
||||
exponentially with the length of the string. This is because matching
|
||||
back references is an NP complete problem, and almost certainly
|
||||
requires exponential time to match in the worst case.
|
||||
|
||||
Predictable and modest memory consumption
|
||||
-----------------------------------------
|
||||
|
||||
A `regexec()` call never allocates memory from the heap. TRE allocates
|
||||
all the memory it needs during a `regcomp()` call, and some temporary
|
||||
working space from the stack frame for the duration of the `regexec()`
|
||||
call. The amount of temporary space needed is constant during
|
||||
matching and does not depend on the searched string. For regexps of
|
||||
reasonable size TRE needs less than 50K of dynamically allocated
|
||||
memory during the `regcomp()` call, less than 20K for the compiled
|
||||
pattern buffer, and less than two kilobytes of temporary working space
|
||||
from the stack frame during a `regexec()` call. There is no time /
|
||||
memory tradeoff. TRE is also small in code size; statically linking
|
||||
with TRE increases the executable size less than 30K (gcc-3.2, x86,
|
||||
GNU/Linux).
|
||||
|
||||
Wide character and multibyte character set support
|
||||
--------------------------------------------------
|
||||
|
||||
TRE supports multibyte character sets. This makes it possible to use
|
||||
regexps seamlessly with, for example, Japanese locales. TRE also
|
||||
provides a wide character API.
|
||||
|
||||
Binary pattern and data support
|
||||
-------------------------------
|
||||
|
||||
TRE provides APIs which allow binary zero characters both in regexps
|
||||
and searched strings. The standard API cannot be easily used to, for
|
||||
example, search for printable words from binary data (although it is
|
||||
possible with some hacking). Searching for patterns which contain
|
||||
binary zeroes embedded is not possible at all with the standard API.
|
||||
|
||||
Completely thread safe
|
||||
----------------------
|
||||
|
||||
TRE is completely thread safe. All the exported functions are
|
||||
re-entrant, and a single compiled regexp object can be used
|
||||
simultaneously in multiple contexts; e.g. in `main()` and a signal
|
||||
handler, or in many threads of a multithreaded application.
|
||||
|
||||
Portable
|
||||
--------
|
||||
|
||||
TRE is portable across multiple platforms. Below is a table of
|
||||
platforms and compilers used to develop and test TRE:
|
||||
|
||||
<table>
|
||||
<tr><th>Platform</th> <th>Compiler</th></tr>
|
||||
<tr><td>FreeBSD 14.1</td> <td>Clang 18</td></tr>
|
||||
<tr><td>Ubuntu 22.04</td> <td>GCC 11</td></tr>
|
||||
<tr><td>macOS 14.6</td> <td>Clang 14</td></tr>
|
||||
<tr><td>Windows 11</td> <td>Microsoft Visual Studio 2022</td></tr>
|
||||
</table>
|
||||
|
||||
TRE should compile without changes on most modern POSIX-like
|
||||
platforms, and be easily portable to any platform with a hosted C
|
||||
implementation.
|
||||
|
||||
Depending on the platform, you may need to install libutf8 to get
|
||||
wide character and multibyte character set support.
|
||||
|
||||
Free
|
||||
----
|
||||
|
||||
TRE is released under a license which is essentially the same as the
|
||||
“2 clause” BSD-style license used in NetBSD. See the file LICENSE for
|
||||
details.
|
||||
|
||||
Roadmap
|
||||
-------
|
||||
|
||||
There are currently two features, both related to collating elements,
|
||||
missing from 100% POSIX compliance. These are:
|
||||
|
||||
* Support for collating elements (e.g. `[[.\<X>.]]`, where `\<X>` is a
|
||||
collating element). It is not possible to support multi-character
|
||||
collating elements portably, since POSIX does not define a way to
|
||||
determine whether a character sequence is a multi-character
|
||||
collating element or not.
|
||||
|
||||
* Support for equivalence classes, for example `[[=\<X>=]]`, where
|
||||
`\<X>` is a collating element. An equivalence class matches any
|
||||
character which has the same primary collation weight as `\<X>`.
|
||||
Again, POSIX provides no portable mechanism for determining the
|
||||
primary collation weight of a collating element.
|
||||
|
||||
Note that other portable regexp implementations don't support
|
||||
collating elements either. The single exception is Regex++, which
|
||||
comes with its own database for collating elements for different
|
||||
locales. Support for collating elements and equivalence classes has
|
||||
not been widely requested and is not very high on the TODO list at the
|
||||
moment.
|
||||
|
||||
These are other features I'm planning to implement real soon now:
|
||||
|
||||
* All the missing GNU extensions enabled in GNU regex, such as
|
||||
`[[:<:]]` and `[[:>:]]`.
|
||||
|
||||
* A `REG_SHORTEST` `regexec()` flag for returning the shortest match
|
||||
instead of the longest match.
|
||||
|
||||
* Perl-compatible syntax:
|
||||
* `[:^class:]`
|
||||
Matches anything but the characters in class. Note that
|
||||
`[^[:class:]]` works already, this would be just a convenience
|
||||
shorthand.
|
||||
|
||||
* `\A`
|
||||
Match only at beginning of string.
|
||||
|
||||
* `\Z`
|
||||
Match only at end of string, or before newline at the end.
|
||||
|
||||
* `\z`
|
||||
Match only at end of string.
|
||||
|
||||
* `\l`
|
||||
Lowercase next char (think vi).
|
||||
|
||||
* `\u`
|
||||
Uppercase next char (think vi).
|
||||
|
||||
* `\L`
|
||||
Lowercase till `\E` (think vi).
|
||||
|
||||
* `\U`
|
||||
Uppercase till `\E` (think vi).
|
||||
|
||||
* `(?=pattern)`
|
||||
Zero-width positive look-ahead assertions.
|
||||
|
||||
* `(?!pattern)`
|
||||
Zero-width negative look-ahead assertions.
|
||||
|
||||
* `(?<=pattern)`
|
||||
Zero-width positive look-behind assertions.
|
||||
|
||||
* `(?<!pattern)`
|
||||
Zero-width negative look-behind assertions.
|
||||
|
||||
Documentation especially for the nonstandard features of TRE, such as
|
||||
approximate matching, is a work in progress (with “progress” loosely
|
||||
defined...) If you want to find an extension to use, reading the
|
||||
`include/tre/tre.h` header might provide some additional hints if you
|
||||
are comfortable with C source code.
|
||||
188
deps/tre/lib/regcomp.c
vendored
Normal file
188
deps/tre/lib/regcomp.c
vendored
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
tre_regcomp.c - TRE POSIX compatible regex compilation functions.
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "tre-internal.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
int
|
||||
tre_regncomp(regex_t *preg, const char *regex, size_t n, int cflags)
|
||||
{
|
||||
int ret;
|
||||
if (n > TRE_MAX_RE)
|
||||
return REG_ESPACE;
|
||||
#if TRE_WCHAR
|
||||
tre_char_t *wregex;
|
||||
size_t wlen;
|
||||
|
||||
wregex = xmalloc(sizeof(tre_char_t) * (n + 1));
|
||||
if (wregex == NULL)
|
||||
return REG_ESPACE;
|
||||
|
||||
/* If the current locale uses the standard single byte encoding of
|
||||
characters, we don't do a multibyte string conversion. If we did,
|
||||
many applications which use the default locale would break since
|
||||
the default "C" locale uses the 7-bit ASCII character set, and
|
||||
all characters with the eighth bit set would be considered invalid. */
|
||||
#if TRE_MULTIBYTE
|
||||
if (TRE_MB_CUR_MAX == 1)
|
||||
#endif /* TRE_MULTIBYTE */
|
||||
{
|
||||
size_t i;
|
||||
const unsigned char *str = (const unsigned char *)regex;
|
||||
tre_char_t *wstr = wregex;
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
*(wstr++) = *(str++);
|
||||
wlen = n;
|
||||
}
|
||||
#if TRE_MULTIBYTE
|
||||
else
|
||||
{
|
||||
size_t consumed;
|
||||
tre_char_t *wcptr = wregex;
|
||||
#ifdef HAVE_MBSTATE_T
|
||||
mbstate_t state;
|
||||
memset(&state, '\0', sizeof(state));
|
||||
#endif /* HAVE_MBSTATE_T */
|
||||
while (n > 0)
|
||||
{
|
||||
consumed = tre_mbrtowc(wcptr, regex, n, &state);
|
||||
|
||||
switch (consumed)
|
||||
{
|
||||
case 0:
|
||||
if (*regex == '\0')
|
||||
consumed = 1;
|
||||
else
|
||||
{
|
||||
xfree(wregex);
|
||||
return REG_BADPAT;
|
||||
}
|
||||
break;
|
||||
case -1:
|
||||
DPRINT(("mbrtowc: error %d: %s.\n", errno, strerror(errno)));
|
||||
xfree(wregex);
|
||||
return REG_BADPAT;
|
||||
case -2:
|
||||
/* The last character wasn't complete. Let's not call it a
|
||||
fatal error. */
|
||||
consumed = n;
|
||||
break;
|
||||
}
|
||||
regex += consumed;
|
||||
n -= consumed;
|
||||
wcptr++;
|
||||
}
|
||||
wlen = wcptr - wregex;
|
||||
}
|
||||
#endif /* TRE_MULTIBYTE */
|
||||
|
||||
wregex[wlen] = L'\0';
|
||||
ret = tre_compile(preg, wregex, wlen, cflags);
|
||||
xfree(wregex);
|
||||
#else /* !TRE_WCHAR */
|
||||
ret = tre_compile(preg, (const tre_char_t *)regex, n, cflags);
|
||||
#endif /* !TRE_WCHAR */
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* this version takes bytes literally, to be used with raw vectors */
|
||||
int
|
||||
tre_regncompb(regex_t *preg, const char *regex, size_t n, int cflags)
|
||||
{
|
||||
int ret;
|
||||
if (n > TRE_MAX_RE)
|
||||
return REG_ESPACE;
|
||||
#if TRE_WCHAR /* wide chars = we need to convert it all to the wide format */
|
||||
tre_char_t *wregex;
|
||||
size_t i;
|
||||
|
||||
wregex = xmalloc(sizeof(tre_char_t) * n);
|
||||
if (wregex == NULL)
|
||||
return REG_ESPACE;
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
wregex[i] = (tre_char_t) ((unsigned char) regex[i]);
|
||||
|
||||
ret = tre_compile(preg, wregex, n, cflags | REG_USEBYTES);
|
||||
xfree(wregex);
|
||||
#else /* !TRE_WCHAR */
|
||||
ret = tre_compile(preg, (const tre_char_t *)regex, n, cflags | REG_USEBYTES);
|
||||
#endif /* !TRE_WCHAR */
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
tre_regcomp(regex_t *preg, const char *regex, int cflags)
|
||||
{
|
||||
size_t n = regex ? strlen(regex) : 0;
|
||||
if (n > TRE_MAX_RE)
|
||||
return REG_ESPACE;
|
||||
return tre_regncomp(preg, regex, n, cflags);
|
||||
}
|
||||
|
||||
int
|
||||
tre_regcompb(regex_t *preg, const char *regex, int cflags)
|
||||
{
|
||||
int ret;
|
||||
tre_char_t *wregex;
|
||||
size_t i, n = regex ? strlen(regex) : 0;
|
||||
const unsigned char *str = (const unsigned char *)regex;
|
||||
tre_char_t *wstr;
|
||||
|
||||
if (n > TRE_MAX_RE)
|
||||
return REG_ESPACE;
|
||||
wregex = xmalloc(sizeof(tre_char_t) * (n + 1));
|
||||
if (wregex == NULL) return REG_ESPACE;
|
||||
wstr = wregex;
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
*(wstr++) = *(str++);
|
||||
wregex[n] = L'\0';
|
||||
ret = tre_compile(preg, wregex, n, cflags | REG_USEBYTES);
|
||||
xfree(wregex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
#ifdef TRE_WCHAR
|
||||
int
|
||||
tre_regwncomp(regex_t *preg, const wchar_t *regex, size_t n, int cflags)
|
||||
{
|
||||
if (n > TRE_MAX_RE)
|
||||
return REG_ESPACE;
|
||||
return tre_compile(preg, regex, n, cflags);
|
||||
}
|
||||
|
||||
int
|
||||
tre_regwcomp(regex_t *preg, const wchar_t *regex, int cflags)
|
||||
{
|
||||
size_t n = regex ? wcslen(regex) : 0;
|
||||
if (n > TRE_MAX_RE)
|
||||
return REG_ESPACE;
|
||||
return tre_compile(preg, regex, n, cflags);
|
||||
}
|
||||
#endif /* TRE_WCHAR */
|
||||
|
||||
void
|
||||
tre_regfree(regex_t *preg)
|
||||
{
|
||||
tre_free(preg);
|
||||
}
|
||||
|
||||
/* EOF */
|
||||
86
deps/tre/lib/regerror.c
vendored
Normal file
86
deps/tre/lib/regerror.c
vendored
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
tre_regerror.c - POSIX tre_regerror() implementation for TRE.
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
|
||||
#include <string.h>
|
||||
#ifdef HAVE_WCHAR_H
|
||||
#include <wchar.h>
|
||||
#endif /* HAVE_WCHAR_H */
|
||||
#ifdef HAVE_WCTYPE_H
|
||||
#include <wctype.h>
|
||||
#endif /* HAVE_WCTYPE_H */
|
||||
|
||||
#include "tre-internal.h"
|
||||
|
||||
#ifdef HAVE_GETTEXT
|
||||
#include <libintl.h>
|
||||
#else
|
||||
#define dgettext(p, s) s
|
||||
#define gettext(s) s
|
||||
#endif
|
||||
|
||||
#define _(String) dgettext(PACKAGE, String)
|
||||
#define gettext_noop(String) String
|
||||
|
||||
#define xstr(s) str(s)
|
||||
#define str(s) #s
|
||||
|
||||
/* Error message strings for error codes listed in `tre.h'. This list
|
||||
needs to be in sync with the codes listed there, naturally. */
|
||||
static const char *tre_error_messages[] =
|
||||
{ gettext_noop("No error"), /* REG_OK */
|
||||
gettext_noop("No match"), /* REG_NOMATCH */
|
||||
gettext_noop("Invalid regexp"), /* REG_BADPAT */
|
||||
gettext_noop("Unknown collating element"), /* REG_ECOLLATE */
|
||||
gettext_noop("Unknown character class name"), /* REG_ECTYPE */
|
||||
gettext_noop("Trailing backslash"), /* REG_EESCAPE */
|
||||
gettext_noop("Invalid back reference"), /* REG_ESUBREG */
|
||||
gettext_noop("Missing ']'"), /* REG_EBRACK */
|
||||
gettext_noop("Missing ')'"), /* REG_EPAREN */
|
||||
gettext_noop("Missing '}'"), /* REG_EBRACE */
|
||||
gettext_noop("Invalid contents of {}"), /* REG_BADBR */
|
||||
gettext_noop("Invalid character range"), /* REG_ERANGE */
|
||||
gettext_noop("Out of memory"), /* REG_ESPACE */
|
||||
gettext_noop("Invalid use of repetition operators"), /* REG_BADRPT */
|
||||
gettext_noop("Maximum repetition in {} larger than " xstr(RE_DUP_MAX)), /* REG_BADMAX */
|
||||
};
|
||||
|
||||
size_t
|
||||
tre_regerror(int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size)
|
||||
{
|
||||
const char *err;
|
||||
size_t err_len;
|
||||
|
||||
/*LINTED*/(void)&preg;
|
||||
if (errcode >= 0
|
||||
&& errcode < (int)(sizeof(tre_error_messages)
|
||||
/ sizeof(*tre_error_messages)))
|
||||
err = gettext(tre_error_messages[errcode]);
|
||||
else
|
||||
err = gettext("Unknown error");
|
||||
|
||||
err_len = strlen(err) + 1;
|
||||
if (errbuf_size > 0 && errbuf != NULL)
|
||||
{
|
||||
if (err_len > errbuf_size)
|
||||
{
|
||||
strncpy(errbuf, err, errbuf_size - 1);
|
||||
errbuf[errbuf_size - 1] = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
strcpy(errbuf, err);
|
||||
}
|
||||
}
|
||||
return err_len;
|
||||
}
|
||||
|
||||
/* EOF */
|
||||
584
deps/tre/lib/regexec.c
vendored
Normal file
584
deps/tre/lib/regexec.c
vendored
Normal file
|
|
@ -0,0 +1,584 @@
|
|||
/*
|
||||
tre_regexec.c - TRE POSIX compatible matching functions (and more).
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
|
||||
#ifdef TRE_USE_ALLOCA
|
||||
/* AIX requires this to be the first thing in the file. */
|
||||
#ifndef __GNUC__
|
||||
# if HAVE_ALLOCA_H
|
||||
# include <alloca.h>
|
||||
# else
|
||||
# ifdef _AIX
|
||||
#pragma alloca
|
||||
# else
|
||||
# ifndef alloca /* predefined by HP cc +Olibcalls */
|
||||
char *alloca ();
|
||||
# endif
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
#endif /* TRE_USE_ALLOCA */
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#ifdef HAVE_WCHAR_H
|
||||
#include <wchar.h>
|
||||
#endif /* HAVE_WCHAR_H */
|
||||
#ifdef HAVE_WCTYPE_H
|
||||
#include <wctype.h>
|
||||
#endif /* HAVE_WCTYPE_H */
|
||||
#ifndef TRE_WCHAR
|
||||
#include <ctype.h>
|
||||
#endif /* !TRE_WCHAR */
|
||||
#ifdef HAVE_MALLOC_H
|
||||
#include <malloc.h>
|
||||
#endif /* HAVE_MALLOC_H */
|
||||
#include <limits.h>
|
||||
|
||||
#include "tre-internal.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
/* Literal alternatives are grouped by the first byte so the matcher can
|
||||
* reach the relevant candidates in O(1). In nocase mode the lookup uses the
|
||||
* same folded byte mapping that was applied at compile time. */
|
||||
static void
|
||||
tre_litopt_candidate_range(const tre_literal_opt_t *opt, unsigned char first_byte,
|
||||
size_t *start, size_t *end)
|
||||
{
|
||||
unsigned char key = opt->nocase ? opt->fold_map[first_byte] : first_byte;
|
||||
*start = opt->start_offsets[key];
|
||||
*end = opt->start_offsets[key + 1];
|
||||
}
|
||||
|
||||
static int
|
||||
tre_litopt_bytes_equal(const unsigned char *haystack,
|
||||
const unsigned char *needle, size_t len,
|
||||
const unsigned char *fold_map)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (fold_map == NULL)
|
||||
return memcmp(haystack, needle, len) == 0;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
if (fold_map[haystack[i]] != needle[i])
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
tre_litopt_contains_case(const unsigned char *haystack, size_t hay_len,
|
||||
const unsigned char *needle, size_t needle_len,
|
||||
int *match_end_ofs)
|
||||
{
|
||||
const unsigned char *p;
|
||||
size_t remaining;
|
||||
|
||||
if (needle_len > hay_len)
|
||||
return 0;
|
||||
|
||||
p = haystack;
|
||||
remaining = hay_len;
|
||||
while (remaining >= needle_len)
|
||||
{
|
||||
p = memchr(p, needle[0], remaining - needle_len + 1);
|
||||
if (p == NULL)
|
||||
return 0;
|
||||
if (memcmp(p, needle, needle_len) == 0)
|
||||
{
|
||||
if (match_end_ofs != NULL)
|
||||
*match_end_ofs = (int)(p - haystack + needle_len);
|
||||
return 1;
|
||||
}
|
||||
remaining = hay_len - (size_t)(p - haystack) - 1;
|
||||
p++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Nocase substring matching is still byte-oriented, but scanning once and
|
||||
* only checking literals that share the same folded first byte avoids the
|
||||
* old O(haystack * literals) restart pattern. */
|
||||
static int
|
||||
tre_litopt_contains_nocase(const tre_literal_opt_t *opt,
|
||||
const unsigned char *haystack, size_t hay_len,
|
||||
int *match_end_ofs)
|
||||
{
|
||||
size_t i, start, end, j;
|
||||
|
||||
for (i = 0; i < hay_len; i++)
|
||||
{
|
||||
tre_litopt_candidate_range(opt, haystack[i], &start, &end);
|
||||
for (j = start; j < end; j++)
|
||||
{
|
||||
const tre_literal_opt_literal_t *lit = &opt->literals[j];
|
||||
if (lit->len <= hay_len - i
|
||||
&& tre_litopt_bytes_equal(haystack + i, lit->data, lit->len,
|
||||
opt->fold_map))
|
||||
{
|
||||
if (match_end_ofs != NULL)
|
||||
*match_end_ofs = (int)(i + lit->len);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static reg_errcode_t
|
||||
tre_match_literal_opt(const tre_tnfa_t *tnfa, const char *string, size_t len,
|
||||
int eflags, int *match_end_ofs)
|
||||
{
|
||||
const tre_literal_opt_t *opt = &tnfa->literal_opt;
|
||||
const unsigned char *haystack = (const unsigned char *)string;
|
||||
size_t start = 0, end = opt->num_literals, i;
|
||||
const unsigned char *fold_map = opt->nocase ? opt->fold_map : NULL;
|
||||
|
||||
if ((opt->mode == TRE_LITERAL_OPT_PREFIX
|
||||
|| opt->mode == TRE_LITERAL_OPT_EXACT)
|
||||
&& (eflags & REG_NOTBOL))
|
||||
return REG_NOMATCH;
|
||||
if ((opt->mode == TRE_LITERAL_OPT_SUFFIX
|
||||
|| opt->mode == TRE_LITERAL_OPT_EXACT)
|
||||
&& (eflags & REG_NOTEOL))
|
||||
return REG_NOMATCH;
|
||||
|
||||
if ((opt->mode == TRE_LITERAL_OPT_EXACT
|
||||
|| opt->mode == TRE_LITERAL_OPT_PREFIX)
|
||||
&& len > 0)
|
||||
tre_litopt_candidate_range(opt, haystack[0], &start, &end);
|
||||
|
||||
if (opt->mode == TRE_LITERAL_OPT_CONTAINS)
|
||||
{
|
||||
if (opt->nocase)
|
||||
return tre_litopt_contains_nocase(opt, haystack, len, match_end_ofs)
|
||||
? REG_OK : REG_NOMATCH;
|
||||
|
||||
for (i = 0; i < opt->num_literals; i++)
|
||||
{
|
||||
const tre_literal_opt_literal_t *lit = &opt->literals[i];
|
||||
if (tre_litopt_contains_case(haystack, len, lit->data, lit->len,
|
||||
match_end_ofs))
|
||||
return REG_OK;
|
||||
}
|
||||
return REG_NOMATCH;
|
||||
}
|
||||
|
||||
for (i = start; i < end; i++)
|
||||
{
|
||||
const tre_literal_opt_literal_t *lit = &opt->literals[i];
|
||||
|
||||
switch (opt->mode)
|
||||
{
|
||||
case TRE_LITERAL_OPT_EXACT:
|
||||
if (len == lit->len
|
||||
&& tre_litopt_bytes_equal(haystack, lit->data, len, fold_map))
|
||||
{
|
||||
if (match_end_ofs != NULL)
|
||||
*match_end_ofs = (int)len;
|
||||
return REG_OK;
|
||||
}
|
||||
break;
|
||||
|
||||
case TRE_LITERAL_OPT_PREFIX:
|
||||
if (len >= lit->len
|
||||
&& tre_litopt_bytes_equal(haystack, lit->data, lit->len,
|
||||
fold_map))
|
||||
{
|
||||
if (match_end_ofs != NULL)
|
||||
*match_end_ofs = (int)lit->len;
|
||||
return REG_OK;
|
||||
}
|
||||
break;
|
||||
|
||||
case TRE_LITERAL_OPT_SUFFIX:
|
||||
if (len >= lit->len
|
||||
&& tre_litopt_bytes_equal(haystack + len - lit->len, lit->data,
|
||||
lit->len, fold_map))
|
||||
{
|
||||
if (match_end_ofs != NULL)
|
||||
*match_end_ofs = (int)len;
|
||||
return REG_OK;
|
||||
}
|
||||
break;
|
||||
|
||||
case TRE_LITERAL_OPT_CONTAINS:
|
||||
case TRE_LITERAL_OPT_NONE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return REG_NOMATCH;
|
||||
}
|
||||
|
||||
|
||||
/* Fills the POSIX.2 regmatch_t array according to the TNFA tag and match
|
||||
endpoint values. */
|
||||
void
|
||||
tre_fill_pmatch(size_t nmatch, regmatch_t pmatch[], int cflags,
|
||||
const tre_tnfa_t *tnfa, int *tags, int match_eo)
|
||||
{
|
||||
tre_submatch_data_t *submatch_data;
|
||||
unsigned int i, j;
|
||||
int *parents;
|
||||
|
||||
i = 0;
|
||||
if (match_eo >= 0 && !(cflags & REG_NOSUB))
|
||||
{
|
||||
/* Construct submatch offsets from the tags. */
|
||||
DPRINT(("end tag = t%d = %d\n", tnfa->end_tag, match_eo));
|
||||
submatch_data = tnfa->submatch_data;
|
||||
while (i < tnfa->num_submatches && i < nmatch)
|
||||
{
|
||||
if (submatch_data[i].so_tag == tnfa->end_tag)
|
||||
pmatch[i].rm_so = match_eo;
|
||||
else
|
||||
pmatch[i].rm_so = tags[submatch_data[i].so_tag];
|
||||
|
||||
if (submatch_data[i].eo_tag == tnfa->end_tag)
|
||||
pmatch[i].rm_eo = match_eo;
|
||||
else
|
||||
pmatch[i].rm_eo = tags[submatch_data[i].eo_tag];
|
||||
|
||||
/* If either of the endpoints were not used, this submatch
|
||||
was not part of the match. */
|
||||
if (pmatch[i].rm_so == -1 || pmatch[i].rm_eo == -1)
|
||||
pmatch[i].rm_so = pmatch[i].rm_eo = -1;
|
||||
|
||||
DPRINT(("pmatch[%d] = {t%d = %d, t%d = %d}\n", i,
|
||||
submatch_data[i].so_tag, pmatch[i].rm_so,
|
||||
submatch_data[i].eo_tag, pmatch[i].rm_eo));
|
||||
i++;
|
||||
}
|
||||
/* Reset all submatches that are not within all of their parent
|
||||
submatches. */
|
||||
i = 0;
|
||||
while (i < tnfa->num_submatches && i < nmatch)
|
||||
{
|
||||
if (pmatch[i].rm_eo == -1)
|
||||
assert(pmatch[i].rm_so == -1);
|
||||
assert(pmatch[i].rm_so <= pmatch[i].rm_eo);
|
||||
|
||||
parents = submatch_data[i].parents;
|
||||
if (parents != NULL)
|
||||
for (j = 0; parents[j] >= 0; j++)
|
||||
{
|
||||
DPRINT(("pmatch[%d] parent %d\n", i, parents[j]));
|
||||
if (pmatch[i].rm_so < pmatch[parents[j]].rm_so
|
||||
|| pmatch[i].rm_eo > pmatch[parents[j]].rm_eo)
|
||||
pmatch[i].rm_so = pmatch[i].rm_eo = -1;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
while (i < nmatch)
|
||||
{
|
||||
pmatch[i].rm_so = -1;
|
||||
pmatch[i].rm_eo = -1;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Wrapper functions for POSIX compatible regexp matching.
|
||||
*/
|
||||
|
||||
int
|
||||
tre_have_backrefs(const regex_t *preg)
|
||||
{
|
||||
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
|
||||
return tnfa->have_backrefs;
|
||||
}
|
||||
|
||||
int
|
||||
tre_have_approx(const regex_t *preg)
|
||||
{
|
||||
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
|
||||
return tnfa->have_approx;
|
||||
}
|
||||
|
||||
static int
|
||||
tre_match(const tre_tnfa_t *tnfa, const void *string, ssize_t len,
|
||||
tre_str_type_t type, size_t nmatch, regmatch_t pmatch[],
|
||||
int eflags)
|
||||
{
|
||||
reg_errcode_t status;
|
||||
int *tags = NULL, eo;
|
||||
if (tnfa->num_tags > 0 && nmatch > 0)
|
||||
{
|
||||
#ifdef TRE_USE_ALLOCA
|
||||
tags = alloca(sizeof(*tags) * tnfa->num_tags);
|
||||
#else /* !TRE_USE_ALLOCA */
|
||||
tags = xmalloc(sizeof(*tags) * tnfa->num_tags);
|
||||
#endif /* !TRE_USE_ALLOCA */
|
||||
if (tags == NULL)
|
||||
return REG_ESPACE;
|
||||
}
|
||||
|
||||
if (type == STR_BYTE
|
||||
&& tnfa->literal_opt.mode != TRE_LITERAL_OPT_NONE
|
||||
&& (nmatch == 0 || (tnfa->cflags & REG_NOSUB))
|
||||
#ifdef TRE_APPROX
|
||||
&& !(eflags & REG_APPROX_MATCHER)
|
||||
#endif /* TRE_APPROX */
|
||||
&& !(eflags & REG_BACKTRACKING_MATCHER))
|
||||
{
|
||||
size_t byte_len = (len >= 0) ? (size_t)len : strlen((const char *)string);
|
||||
status = tre_match_literal_opt(tnfa, string, byte_len, eflags, &eo);
|
||||
|
||||
/* Even when the caller asked for no submatches, regexec() still has to
|
||||
* clear any pmatch entries it was handed. The normal matcher path does
|
||||
* this through tre_fill_pmatch(), so mirror that behavior here. */
|
||||
if (status == REG_OK && nmatch > 0)
|
||||
tre_fill_pmatch(nmatch, pmatch, tnfa->cflags, tnfa, NULL, eo);
|
||||
|
||||
#ifndef TRE_USE_ALLOCA
|
||||
if (tags)
|
||||
xfree(tags);
|
||||
#endif /* !TRE_USE_ALLOCA */
|
||||
return status;
|
||||
}
|
||||
|
||||
/* Dispatch to the appropriate matcher. */
|
||||
if (tnfa->have_backrefs || eflags & REG_BACKTRACKING_MATCHER)
|
||||
{
|
||||
/* The regex has back references, use the backtracking matcher. */
|
||||
if (type == STR_USER)
|
||||
{
|
||||
const tre_str_source *source = string;
|
||||
if (source->rewind == NULL || source->compare == NULL)
|
||||
{
|
||||
/* The backtracking matcher requires rewind and compare
|
||||
capabilities from the input stream. */
|
||||
#ifndef TRE_USE_ALLOCA
|
||||
if (tags)
|
||||
xfree(tags);
|
||||
#endif /* !TRE_USE_ALLOCA */
|
||||
return REG_BADPAT;
|
||||
}
|
||||
}
|
||||
status = tre_tnfa_run_backtrack(tnfa, string, len, type,
|
||||
tags, eflags, &eo);
|
||||
}
|
||||
#ifdef TRE_APPROX
|
||||
else if (tnfa->have_approx || eflags & REG_APPROX_MATCHER)
|
||||
{
|
||||
/* The regex uses approximate matching, use the approximate matcher. */
|
||||
regamatch_t match;
|
||||
regaparams_t params;
|
||||
tre_regaparams_default(¶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 */
|
||||
226
deps/tre/lib/tre-ast.c
vendored
Normal file
226
deps/tre/lib/tre-ast.c
vendored
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
/*
|
||||
tre-ast.c - Abstract syntax tree (AST) routines
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
#include <assert.h>
|
||||
|
||||
#include "tre-ast.h"
|
||||
#include "tre-mem.h"
|
||||
|
||||
tre_ast_node_t *
|
||||
tre_ast_new_node(tre_mem_t mem, tre_ast_type_t type, size_t size)
|
||||
{
|
||||
tre_ast_node_t *node;
|
||||
|
||||
node = tre_mem_calloc(mem, sizeof(*node));
|
||||
if (!node)
|
||||
return NULL;
|
||||
node->obj = tre_mem_calloc(mem, size);
|
||||
if (!node->obj)
|
||||
return NULL;
|
||||
node->type = type;
|
||||
node->nullable = -1;
|
||||
node->submatch_id = -1;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
tre_ast_node_t *
|
||||
tre_ast_new_literal(tre_mem_t mem, int code_min, int code_max)
|
||||
{
|
||||
tre_ast_node_t *node;
|
||||
tre_literal_t *lit;
|
||||
|
||||
node = tre_ast_new_node(mem, LITERAL, sizeof(tre_literal_t));
|
||||
if (!node)
|
||||
return NULL;
|
||||
lit = node->obj;
|
||||
lit->code_min = code_min;
|
||||
lit->code_max = code_max;
|
||||
lit->position = -1;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
tre_ast_node_t *
|
||||
tre_ast_new_iter(tre_mem_t mem, tre_ast_node_t *arg, int min, int max,
|
||||
int minimal)
|
||||
{
|
||||
tre_ast_node_t *node;
|
||||
tre_iteration_t *iter;
|
||||
|
||||
node = tre_ast_new_node(mem, ITERATION, sizeof(tre_iteration_t));
|
||||
if (!node)
|
||||
return NULL;
|
||||
iter = node->obj;
|
||||
iter->arg = arg;
|
||||
iter->min = min;
|
||||
iter->max = max;
|
||||
iter->minimal = minimal;
|
||||
node->num_submatches = arg->num_submatches;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
tre_ast_node_t *
|
||||
tre_ast_new_union(tre_mem_t mem, tre_ast_node_t *left, tre_ast_node_t *right)
|
||||
{
|
||||
tre_ast_node_t *node;
|
||||
|
||||
node = tre_ast_new_node(mem, UNION, sizeof(tre_union_t));
|
||||
if (node == NULL)
|
||||
return NULL;
|
||||
((tre_union_t *)node->obj)->left = left;
|
||||
((tre_union_t *)node->obj)->right = right;
|
||||
node->num_submatches = left->num_submatches + right->num_submatches;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
tre_ast_node_t *
|
||||
tre_ast_new_catenation(tre_mem_t mem, tre_ast_node_t *left,
|
||||
tre_ast_node_t *right)
|
||||
{
|
||||
tre_ast_node_t *node;
|
||||
|
||||
node = tre_ast_new_node(mem, CATENATION, sizeof(tre_catenation_t));
|
||||
if (node == NULL)
|
||||
return NULL;
|
||||
((tre_catenation_t *)node->obj)->left = left;
|
||||
((tre_catenation_t *)node->obj)->right = right;
|
||||
node->num_submatches = left->num_submatches + right->num_submatches;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
#ifdef TRE_DEBUG
|
||||
|
||||
static void
|
||||
tre_findent(FILE *stream, int i)
|
||||
{
|
||||
while (i-- > 0)
|
||||
fputc(' ', stream);
|
||||
}
|
||||
|
||||
void
|
||||
tre_print_params(int *params)
|
||||
{
|
||||
int i;
|
||||
if (params)
|
||||
{
|
||||
DPRINT(("params ["));
|
||||
for (i = 0; i < TRE_PARAM_LAST; i++)
|
||||
{
|
||||
if (params[i] == TRE_PARAM_UNSET)
|
||||
DPRINT(("unset"));
|
||||
else if (params[i] == TRE_PARAM_DEFAULT)
|
||||
DPRINT(("default"));
|
||||
else
|
||||
DPRINT(("%d", params[i]));
|
||||
if (i < TRE_PARAM_LAST - 1)
|
||||
DPRINT((", "));
|
||||
}
|
||||
DPRINT(("]"));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
tre_do_print(FILE *stream, tre_ast_node_t *ast, int indent)
|
||||
{
|
||||
int code_min, code_max, pos;
|
||||
int num_tags = ast->num_tags;
|
||||
tre_literal_t *lit;
|
||||
tre_iteration_t *iter;
|
||||
|
||||
tre_findent(stream, indent);
|
||||
switch (ast->type)
|
||||
{
|
||||
case LITERAL:
|
||||
lit = ast->obj;
|
||||
code_min = lit->code_min;
|
||||
code_max = lit->code_max;
|
||||
pos = lit->position;
|
||||
if (IS_EMPTY(lit))
|
||||
{
|
||||
fprintf(stream, "literal empty\n");
|
||||
}
|
||||
else if (IS_ASSERTION(lit))
|
||||
{
|
||||
int i;
|
||||
char *assertions[] = { "bol", "eol", "ctype", "!ctype",
|
||||
"bow", "eow", "wb", "!wb" };
|
||||
if (code_max >= ASSERT_LAST << 1)
|
||||
assert(0);
|
||||
fprintf(stream, "assertions: ");
|
||||
for (i = 0; (1 << i) <= ASSERT_LAST; i++)
|
||||
if (code_max & (1 << i))
|
||||
fprintf(stream, "%s ", assertions[i]);
|
||||
fprintf(stream, "\n");
|
||||
}
|
||||
else if (IS_TAG(lit))
|
||||
{
|
||||
fprintf(stream, "tag %d\n", code_max);
|
||||
}
|
||||
else if (IS_BACKREF(lit))
|
||||
{
|
||||
fprintf(stream, "backref %d, pos %d\n", code_max, pos);
|
||||
}
|
||||
else if (IS_PARAMETER(lit))
|
||||
{
|
||||
tre_print_params(lit->u.params);
|
||||
fprintf(stream, "\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stream, "literal (%c, %c) (%d, %d), pos %d, sub %d, "
|
||||
"%d tags\n", code_min, code_max, code_min, code_max, pos,
|
||||
ast->submatch_id, num_tags);
|
||||
}
|
||||
break;
|
||||
case ITERATION:
|
||||
iter = ast->obj;
|
||||
fprintf(stream, "iteration {%d, %d}, sub %d, %d tags, %s\n",
|
||||
iter->min, iter->max, ast->submatch_id, num_tags,
|
||||
iter->minimal ? "minimal" : "greedy");
|
||||
tre_do_print(stream, iter->arg, indent + 2);
|
||||
break;
|
||||
case UNION:
|
||||
fprintf(stream, "union, sub %d, %d tags\n", ast->submatch_id, num_tags);
|
||||
tre_do_print(stream, ((tre_union_t *)ast->obj)->left, indent + 2);
|
||||
tre_do_print(stream, ((tre_union_t *)ast->obj)->right, indent + 2);
|
||||
break;
|
||||
case CATENATION:
|
||||
fprintf(stream, "catenation, sub %d, %d tags\n", ast->submatch_id,
|
||||
num_tags);
|
||||
tre_do_print(stream, ((tre_catenation_t *)ast->obj)->left, indent + 2);
|
||||
tre_do_print(stream, ((tre_catenation_t *)ast->obj)->right, indent + 2);
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
tre_ast_fprint(FILE *stream, tre_ast_node_t *ast)
|
||||
{
|
||||
tre_do_print(stream, ast, 0);
|
||||
}
|
||||
|
||||
void
|
||||
tre_ast_print(tre_ast_node_t *tree)
|
||||
{
|
||||
printf("AST:\n");
|
||||
tre_ast_fprint(stdout, tree);
|
||||
}
|
||||
|
||||
#endif /* TRE_DEBUG */
|
||||
|
||||
/* EOF */
|
||||
128
deps/tre/lib/tre-ast.h
vendored
Normal file
128
deps/tre/lib/tre-ast.h
vendored
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
tre-ast.h - Abstract syntax tree (AST) definitions
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#ifndef TRE_AST_H
|
||||
#define TRE_AST_H 1
|
||||
|
||||
#include "tre-mem.h"
|
||||
#include "tre-internal.h"
|
||||
#include "tre-compile.h"
|
||||
|
||||
/* The different AST node types. */
|
||||
typedef enum {
|
||||
LITERAL,
|
||||
CATENATION,
|
||||
ITERATION,
|
||||
UNION
|
||||
} tre_ast_type_t;
|
||||
|
||||
/* Special subtypes of TRE_LITERAL. */
|
||||
#define EMPTY -1 /* Empty leaf (denotes empty string). */
|
||||
#define ASSERTION -2 /* Assertion leaf. */
|
||||
#define TAG -3 /* Tag leaf. */
|
||||
#define BACKREF -4 /* Back reference leaf. */
|
||||
#define PARAMETER -5 /* Parameter. */
|
||||
|
||||
#define IS_SPECIAL(x) ((x)->code_min < 0)
|
||||
#define IS_EMPTY(x) ((x)->code_min == EMPTY)
|
||||
#define IS_ASSERTION(x) ((x)->code_min == ASSERTION)
|
||||
#define IS_TAG(x) ((x)->code_min == TAG)
|
||||
#define IS_BACKREF(x) ((x)->code_min == BACKREF)
|
||||
#define IS_PARAMETER(x) ((x)->code_min == PARAMETER)
|
||||
|
||||
|
||||
/* A generic AST node. All AST nodes consist of this node on the top
|
||||
level with `obj' pointing to the actual content. */
|
||||
typedef struct {
|
||||
tre_ast_type_t type; /* Type of the node. */
|
||||
void *obj; /* Pointer to actual node. */
|
||||
int nullable;
|
||||
int submatch_id;
|
||||
unsigned int num_submatches;
|
||||
unsigned int num_tags;
|
||||
tre_pos_and_tags_t *firstpos;
|
||||
tre_pos_and_tags_t *lastpos;
|
||||
} tre_ast_node_t;
|
||||
|
||||
|
||||
/* A "literal" node. These are created for assertions, back references,
|
||||
tags, matching parameter settings, and all expressions that match one
|
||||
character. */
|
||||
typedef struct {
|
||||
long code_min;
|
||||
long code_max;
|
||||
int position;
|
||||
union {
|
||||
tre_ctype_t class;
|
||||
int *params;
|
||||
} u;
|
||||
tre_ctype_t *neg_classes;
|
||||
} tre_literal_t;
|
||||
|
||||
/* A "catenation" node. These are created when two regexps are concatenated.
|
||||
If there are more than one subexpressions in sequence, the `left' part
|
||||
holds all but the last, and `right' part holds the last subexpression
|
||||
(catenation is left associative). */
|
||||
typedef struct {
|
||||
tre_ast_node_t *left;
|
||||
tre_ast_node_t *right;
|
||||
} tre_catenation_t;
|
||||
|
||||
/* An "iteration" node. These are created for the "*", "+", "?", and "{m,n}"
|
||||
operators. */
|
||||
typedef struct {
|
||||
/* Subexpression to match. */
|
||||
tre_ast_node_t *arg;
|
||||
/* Minimum number of consecutive matches. */
|
||||
int min;
|
||||
/* Maximum number of consecutive matches. */
|
||||
int max;
|
||||
/* If 0, match as many characters as possible, if 1 match as few as
|
||||
possible. Note that this does not always mean the same thing as
|
||||
matching as many/few repetitions as possible. */
|
||||
unsigned int minimal:1;
|
||||
/* Approximate matching parameters (or NULL). */
|
||||
int *params;
|
||||
} tre_iteration_t;
|
||||
|
||||
/* An "union" node. These are created for the "|" operator. */
|
||||
typedef struct {
|
||||
tre_ast_node_t *left;
|
||||
tre_ast_node_t *right;
|
||||
} tre_union_t;
|
||||
|
||||
tre_ast_node_t *
|
||||
tre_ast_new_node(tre_mem_t mem, tre_ast_type_t type, size_t size);
|
||||
|
||||
tre_ast_node_t *
|
||||
tre_ast_new_literal(tre_mem_t mem, int code_min, int code_max);
|
||||
|
||||
tre_ast_node_t *
|
||||
tre_ast_new_iter(tre_mem_t mem, tre_ast_node_t *arg, int min, int max,
|
||||
int minimal);
|
||||
|
||||
tre_ast_node_t *
|
||||
tre_ast_new_union(tre_mem_t mem, tre_ast_node_t *left, tre_ast_node_t *right);
|
||||
|
||||
tre_ast_node_t *
|
||||
tre_ast_new_catenation(tre_mem_t mem, tre_ast_node_t *left,
|
||||
tre_ast_node_t *right);
|
||||
|
||||
#ifdef TRE_DEBUG
|
||||
void
|
||||
tre_ast_print(tre_ast_node_t *tree);
|
||||
|
||||
/* XXX - rethink AST printing API */
|
||||
void
|
||||
tre_print_params(int *params);
|
||||
#endif /* TRE_DEBUG */
|
||||
|
||||
#endif /* TRE_AST_H */
|
||||
|
||||
/* EOF */
|
||||
2673
deps/tre/lib/tre-compile.c
vendored
Normal file
2673
deps/tre/lib/tre-compile.c
vendored
Normal file
File diff suppressed because it is too large
Load diff
27
deps/tre/lib/tre-compile.h
vendored
Normal file
27
deps/tre/lib/tre-compile.h
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
tre-compile.h: Regex compilation definitions
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#ifndef TRE_COMPILE_H
|
||||
#define TRE_COMPILE_H 1
|
||||
|
||||
typedef struct {
|
||||
int position;
|
||||
int code_min;
|
||||
int code_max;
|
||||
int *tags;
|
||||
int assertions;
|
||||
tre_ctype_t class;
|
||||
tre_ctype_t *neg_classes;
|
||||
int backref;
|
||||
int *params;
|
||||
} tre_pos_and_tags_t;
|
||||
|
||||
#endif /* TRE_COMPILE_H */
|
||||
|
||||
/* EOF */
|
||||
73
deps/tre/lib/tre-filter.c
vendored
Normal file
73
deps/tre/lib/tre-filter.c
vendored
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
tre-filter.c: Histogram filter to quickly find regexp match candidates
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
/* The idea of this filter is quite simple. First, let's assume the
|
||||
search pattern is a simple string. In order for a substring of a
|
||||
longer string to match the search pattern, it must have the same
|
||||
numbers of different characters as the pattern, and those
|
||||
characters must occur in the same order as they occur in pattern. */
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
#include <stdio.h>
|
||||
#include "tre-internal.h"
|
||||
#include "tre-filter.h"
|
||||
|
||||
int
|
||||
tre_filter_find(const unsigned char *str, size_t len, tre_filter_t *filter)
|
||||
{
|
||||
unsigned short counts[256];
|
||||
unsigned int i;
|
||||
unsigned int window_len = filter->window_len;
|
||||
tre_filter_profile_t *profile = filter->profile;
|
||||
const unsigned char *str_orig = str;
|
||||
|
||||
DPRINT(("tre_filter_find: %.*s\n", len, str));
|
||||
|
||||
for (i = 0; i < elementsof(counts); i++)
|
||||
counts[i] = 0;
|
||||
|
||||
i = 0;
|
||||
while (*str && i < window_len && i < len)
|
||||
{
|
||||
counts[*str]++;
|
||||
i++;
|
||||
str++;
|
||||
len--;
|
||||
}
|
||||
|
||||
while (len > 0)
|
||||
{
|
||||
tre_filter_profile_t *p;
|
||||
counts[*str]++;
|
||||
counts[*(str - window_len)]--;
|
||||
|
||||
p = profile;
|
||||
while (p->ch)
|
||||
{
|
||||
if (counts[p->ch] < p->count)
|
||||
break;
|
||||
p++;
|
||||
}
|
||||
if (!p->ch)
|
||||
{
|
||||
DPRINT(("Found possible match at %d\n",
|
||||
str - str_orig));
|
||||
return str - str_orig;
|
||||
}
|
||||
else
|
||||
{
|
||||
DPRINT(("No match so far...\n"));
|
||||
}
|
||||
len--;
|
||||
str++;
|
||||
}
|
||||
DPRINT(("This string cannot match.\n"));
|
||||
return -1;
|
||||
}
|
||||
19
deps/tre/lib/tre-filter.h
vendored
Normal file
19
deps/tre/lib/tre-filter.h
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
|
||||
|
||||
|
||||
|
||||
typedef struct {
|
||||
unsigned char ch;
|
||||
unsigned char count;
|
||||
} tre_filter_profile_t;
|
||||
|
||||
typedef struct {
|
||||
/* Length of the window where the character counts are kept. */
|
||||
int window_len;
|
||||
/* Required character counts table. */
|
||||
tre_filter_profile_t *profile;
|
||||
} tre_filter_t;
|
||||
|
||||
|
||||
int
|
||||
tre_filter_find(const unsigned char *str, size_t len, tre_filter_t *filter);
|
||||
319
deps/tre/lib/tre-internal.h
vendored
Normal file
319
deps/tre/lib/tre-internal.h
vendored
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
/*
|
||||
tre-internal.h - TRE internal definitions
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef TRE_INTERNAL_H
|
||||
#define TRE_INTERNAL_H 1
|
||||
|
||||
#ifdef HAVE_WCHAR_H
|
||||
#include <wchar.h>
|
||||
#endif /* HAVE_WCHAR_H */
|
||||
|
||||
#ifdef HAVE_WCTYPE_H
|
||||
#include <wctype.h>
|
||||
#endif /* HAVE_WCTYPE_H */
|
||||
|
||||
#ifdef HAVE_SYS_TYPES_H
|
||||
#include <sys/types.h>
|
||||
#endif /* HAVE_SYS_TYPES_H */
|
||||
|
||||
#include <limits.h>
|
||||
#include <ctype.h>
|
||||
#include "../local_includes/tre.h"
|
||||
|
||||
#define TRE_MAX_RE 65536
|
||||
#define TRE_MAX_STRING INT_MAX
|
||||
#define TRE_MAX_STACK 1048576
|
||||
|
||||
#ifdef TRE_DEBUG
|
||||
#include <stdio.h>
|
||||
#define DPRINT(msg) do {printf msg; fflush(stdout);} while(/*CONSTCOND*/(void)0,0)
|
||||
#else /* !TRE_DEBUG */
|
||||
#define DPRINT(msg) do { } while(/*CONSTCOND*/(void)0,0)
|
||||
#endif /* !TRE_DEBUG */
|
||||
|
||||
#define elementsof(x) ( sizeof(x) / sizeof(x[0]) )
|
||||
|
||||
#ifdef HAVE_MBRTOWC
|
||||
#define tre_mbrtowc(pwc, s, n, ps) (mbrtowc((pwc), (s), (n), (ps)))
|
||||
#else /* !HAVE_MBRTOWC */
|
||||
#ifdef HAVE_MBTOWC
|
||||
#define tre_mbrtowc(pwc, s, n, ps) (mbtowc((pwc), (s), (n)))
|
||||
#endif /* HAVE_MBTOWC */
|
||||
#endif /* !HAVE_MBRTOWC */
|
||||
|
||||
#ifdef TRE_MULTIBYTE
|
||||
#ifdef HAVE_MBSTATE_T
|
||||
#define TRE_MBSTATE
|
||||
#endif /* TRE_MULTIBYTE */
|
||||
#endif /* HAVE_MBSTATE_T */
|
||||
|
||||
/* Define the character types and functions. */
|
||||
#ifdef TRE_WCHAR
|
||||
|
||||
/* Wide characters. */
|
||||
typedef wint_t tre_cint_t;
|
||||
#if WCHAR_MAX <= INT_MAX
|
||||
#define TRE_CHAR_MAX WCHAR_MAX
|
||||
#else /* WCHAR_MAX > INT_MAX */
|
||||
#define TRE_CHAR_MAX INT_MAX
|
||||
#endif
|
||||
|
||||
#ifdef TRE_MULTIBYTE
|
||||
#define TRE_MB_CUR_MAX MB_CUR_MAX
|
||||
#else /* !TRE_MULTIBYTE */
|
||||
#define TRE_MB_CUR_MAX 1
|
||||
#endif /* !TRE_MULTIBYTE */
|
||||
|
||||
#define tre_isalnum iswalnum
|
||||
#define tre_isalpha iswalpha
|
||||
#ifdef HAVE_ISWBLANK
|
||||
#define tre_isblank iswblank
|
||||
#endif /* HAVE_ISWBLANK */
|
||||
#define tre_iscntrl iswcntrl
|
||||
#define tre_isdigit iswdigit
|
||||
#define tre_isgraph iswgraph
|
||||
#define tre_islower iswlower
|
||||
#define tre_isprint iswprint
|
||||
#define tre_ispunct iswpunct
|
||||
#define tre_isspace iswspace
|
||||
#define tre_isupper iswupper
|
||||
#define tre_isxdigit iswxdigit
|
||||
|
||||
#define tre_tolower towlower
|
||||
#define tre_toupper towupper
|
||||
#define tre_strlen wcslen
|
||||
|
||||
#else /* !TRE_WCHAR */
|
||||
|
||||
/* 8 bit characters. */
|
||||
typedef short tre_cint_t;
|
||||
#define TRE_CHAR_MAX 255
|
||||
#define TRE_MB_CUR_MAX 1
|
||||
|
||||
#define tre_isalnum isalnum
|
||||
#define tre_isalpha isalpha
|
||||
#ifdef HAVE_ISASCII
|
||||
#define tre_isascii isascii
|
||||
#endif /* HAVE_ISASCII */
|
||||
#ifdef HAVE_ISBLANK
|
||||
#define tre_isblank isblank
|
||||
#endif /* HAVE_ISBLANK */
|
||||
#define tre_iscntrl iscntrl
|
||||
#define tre_isdigit isdigit
|
||||
#define tre_isgraph isgraph
|
||||
#define tre_islower islower
|
||||
#define tre_isprint isprint
|
||||
#define tre_ispunct ispunct
|
||||
#define tre_isspace isspace
|
||||
#define tre_isupper isupper
|
||||
#define tre_isxdigit isxdigit
|
||||
|
||||
#define tre_tolower(c) (tre_cint_t)(tolower(c))
|
||||
#define tre_toupper(c) (tre_cint_t)(toupper(c))
|
||||
#define tre_strlen(s) (strlen((const char*)s))
|
||||
|
||||
#endif /* !TRE_WCHAR */
|
||||
|
||||
#if defined(TRE_WCHAR) && defined(HAVE_ISWCTYPE) && defined(HAVE_WCTYPE)
|
||||
#define TRE_USE_SYSTEM_WCTYPE 1
|
||||
#endif
|
||||
|
||||
#ifdef TRE_USE_SYSTEM_WCTYPE
|
||||
/* Use system provided iswctype() and wctype(). */
|
||||
typedef wctype_t tre_ctype_t;
|
||||
#define tre_isctype iswctype
|
||||
#define tre_ctype wctype
|
||||
#else /* !TRE_USE_SYSTEM_WCTYPE */
|
||||
/* Define our own versions of iswctype() and wctype(). */
|
||||
typedef int (*tre_ctype_t)(tre_cint_t);
|
||||
#define tre_isctype(c, type) ( (type)(c) )
|
||||
tre_ctype_t tre_ctype(const char *name);
|
||||
#endif /* !TRE_USE_SYSTEM_WCTYPE */
|
||||
|
||||
typedef enum { STR_WIDE, STR_BYTE, STR_MBS, STR_USER } tre_str_type_t;
|
||||
|
||||
/* Returns number of bytes to add to (char *)ptr to make it
|
||||
properly aligned for the type. */
|
||||
#define ALIGN(ptr, type) \
|
||||
((((long)ptr) % sizeof(type)) \
|
||||
? (sizeof(type) - (((long)ptr) % sizeof(type))) \
|
||||
: 0)
|
||||
|
||||
#undef MAX
|
||||
#undef MIN
|
||||
#define MAX(a, b) (((a) >= (b)) ? (a) : (b))
|
||||
#define MIN(a, b) (((a) <= (b)) ? (a) : (b))
|
||||
|
||||
/* Define STRF to the correct printf formatter for strings. */
|
||||
#ifdef TRE_WCHAR
|
||||
#define STRF "ls"
|
||||
#else /* !TRE_WCHAR */
|
||||
#define STRF "s"
|
||||
#endif /* !TRE_WCHAR */
|
||||
|
||||
/* TNFA transition type. A TNFA state is an array of transitions,
|
||||
the terminator is a transition with NULL `state'. */
|
||||
typedef struct tnfa_transition tre_tnfa_transition_t;
|
||||
|
||||
struct tnfa_transition {
|
||||
/* Range of accepted characters. */
|
||||
tre_cint_t code_min;
|
||||
tre_cint_t code_max;
|
||||
/* Pointer to the destination state. */
|
||||
tre_tnfa_transition_t *state;
|
||||
/* ID number of the destination state. */
|
||||
int state_id;
|
||||
/* -1 terminated array of tags (or NULL). */
|
||||
int *tags;
|
||||
/* Matching parameters settings (or NULL). */
|
||||
int *params;
|
||||
/* Assertion bitmap. */
|
||||
int assertions;
|
||||
/* Assertion parameters. */
|
||||
union {
|
||||
/* Character class assertion. */
|
||||
tre_ctype_t class;
|
||||
/* Back reference assertion. */
|
||||
int backref;
|
||||
} u;
|
||||
/* Negative character class assertions. */
|
||||
tre_ctype_t *neg_classes;
|
||||
};
|
||||
|
||||
|
||||
/* Assertions. */
|
||||
#define ASSERT_AT_BOL 1 /* Beginning of line. */
|
||||
#define ASSERT_AT_EOL 2 /* End of line. */
|
||||
#define ASSERT_CHAR_CLASS 4 /* Character class in `class'. */
|
||||
#define ASSERT_CHAR_CLASS_NEG 8 /* Character classes in `neg_classes'. */
|
||||
#define ASSERT_AT_BOW 16 /* Beginning of word. */
|
||||
#define ASSERT_AT_EOW 32 /* End of word. */
|
||||
#define ASSERT_AT_WB 64 /* Word boundary. */
|
||||
#define ASSERT_AT_WB_NEG 128 /* Not a word boundary. */
|
||||
#define ASSERT_BACKREF 256 /* A back reference in `backref'. */
|
||||
#define ASSERT_LAST 256
|
||||
|
||||
/* Tag directions. */
|
||||
typedef enum {
|
||||
TRE_TAG_MINIMIZE = 0,
|
||||
TRE_TAG_MAXIMIZE = 1
|
||||
} tre_tag_direction_t;
|
||||
|
||||
/* Parameters that can be changed dynamically while matching. */
|
||||
typedef enum {
|
||||
TRE_PARAM_COST_INS = 0,
|
||||
TRE_PARAM_COST_DEL = 1,
|
||||
TRE_PARAM_COST_SUBST = 2,
|
||||
TRE_PARAM_COST_MAX = 3,
|
||||
TRE_PARAM_MAX_INS = 4,
|
||||
TRE_PARAM_MAX_DEL = 5,
|
||||
TRE_PARAM_MAX_SUBST = 6,
|
||||
TRE_PARAM_MAX_ERR = 7,
|
||||
TRE_PARAM_DEPTH = 8,
|
||||
TRE_PARAM_LAST = 9
|
||||
} tre_param_t;
|
||||
|
||||
/* Unset matching parameter */
|
||||
#define TRE_PARAM_UNSET -1
|
||||
|
||||
/* Signifies the default matching parameter value. */
|
||||
#define TRE_PARAM_DEFAULT -2
|
||||
|
||||
/* Instructions to compute submatch register values from tag values
|
||||
after a successful match. */
|
||||
struct tre_submatch_data {
|
||||
/* Tag that gives the value for rm_so (submatch start offset). */
|
||||
int so_tag;
|
||||
/* Tag that gives the value for rm_eo (submatch end offset). */
|
||||
int eo_tag;
|
||||
/* List of submatches this submatch is contained in. */
|
||||
int *parents;
|
||||
};
|
||||
|
||||
typedef struct tre_submatch_data tre_submatch_data_t;
|
||||
|
||||
typedef enum {
|
||||
TRE_LITERAL_OPT_NONE = 0,
|
||||
TRE_LITERAL_OPT_CONTAINS,
|
||||
TRE_LITERAL_OPT_PREFIX,
|
||||
TRE_LITERAL_OPT_SUFFIX,
|
||||
TRE_LITERAL_OPT_EXACT
|
||||
} tre_literal_opt_mode_t;
|
||||
|
||||
typedef struct {
|
||||
unsigned char *data;
|
||||
size_t len;
|
||||
} tre_literal_opt_literal_t;
|
||||
|
||||
typedef struct {
|
||||
tre_literal_opt_mode_t mode;
|
||||
int nocase;
|
||||
size_t num_literals;
|
||||
/* Folded byte mapping used by the nocase fast path. */
|
||||
unsigned char fold_map[256];
|
||||
/* Literal index ranges grouped by the first literal byte. */
|
||||
size_t start_offsets[257];
|
||||
tre_literal_opt_literal_t *literals;
|
||||
} tre_literal_opt_t;
|
||||
|
||||
|
||||
/* TNFA definition. */
|
||||
typedef struct tnfa tre_tnfa_t;
|
||||
|
||||
struct tnfa {
|
||||
tre_tnfa_transition_t *transitions;
|
||||
unsigned int num_transitions;
|
||||
tre_tnfa_transition_t *initial;
|
||||
tre_tnfa_transition_t *final;
|
||||
tre_submatch_data_t *submatch_data;
|
||||
char *firstpos_chars;
|
||||
int first_char;
|
||||
unsigned int num_submatches;
|
||||
tre_tag_direction_t *tag_directions;
|
||||
int *minimal_tags;
|
||||
int num_tags;
|
||||
int num_minimals;
|
||||
int end_tag;
|
||||
int num_states;
|
||||
int cflags;
|
||||
int have_backrefs;
|
||||
int have_approx;
|
||||
int params_depth;
|
||||
tre_literal_opt_t literal_opt;
|
||||
};
|
||||
|
||||
int
|
||||
tre_compile(regex_t *preg, const tre_char_t *regex, size_t n, int cflags);
|
||||
|
||||
void
|
||||
tre_free(regex_t *preg);
|
||||
|
||||
void
|
||||
tre_fill_pmatch(size_t nmatch, regmatch_t pmatch[], int cflags,
|
||||
const tre_tnfa_t *tnfa, int *tags, int match_eo);
|
||||
|
||||
reg_errcode_t
|
||||
tre_tnfa_run_parallel(const tre_tnfa_t *tnfa, const void *string, ssize_t len,
|
||||
tre_str_type_t type, int *match_tags, int eflags,
|
||||
int *match_end_ofs);
|
||||
|
||||
reg_errcode_t
|
||||
tre_tnfa_run_backtrack(const tre_tnfa_t *tnfa, const void *string, ssize_t len,
|
||||
tre_str_type_t type, int *match_tags, int eflags,
|
||||
int *match_end_ofs);
|
||||
|
||||
#ifdef TRE_APPROX
|
||||
reg_errcode_t
|
||||
tre_tnfa_run_approx(const tre_tnfa_t *tnfa, const void *string, ssize_t len,
|
||||
tre_str_type_t type, int *match_tags, regamatch_t *match,
|
||||
regaparams_t params, int eflags, int *match_end_ofs);
|
||||
#endif /* TRE_APPROX */
|
||||
|
||||
#endif /* TRE_INTERNAL_H */
|
||||
|
||||
/* EOF */
|
||||
676
deps/tre/lib/tre-match-backtrack.c
vendored
Normal file
676
deps/tre/lib/tre-match-backtrack.c
vendored
Normal file
|
|
@ -0,0 +1,676 @@
|
|||
/*
|
||||
tre-match-backtrack.c - TRE backtracking regex matching engine
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
This matcher is for regexps that use back referencing. Regexp matching
|
||||
with back referencing is an NP-complete problem on the number of back
|
||||
references. The easiest way to match them is to use a backtracking
|
||||
routine which basically goes through all possible paths in the TNFA
|
||||
and chooses the one which results in the best (leftmost and longest)
|
||||
match. This can be spectacularly expensive and may run out of stack
|
||||
space, but there really is no better known generic algorithm. Quoting
|
||||
Henry Spencer from comp.compilers:
|
||||
<URL: http://compilers.iecc.com/comparch/article/93-03-102>
|
||||
|
||||
POSIX.2 REs require longest match, which is really exciting to
|
||||
implement since the obsolete ("basic") variant also includes
|
||||
\<digit>. I haven't found a better way of tackling this than doing
|
||||
a preliminary match using a DFA (or simulation) on a modified RE
|
||||
that just replicates subREs for \<digit>, and then doing a
|
||||
backtracking match to determine whether the subRE matches were
|
||||
right. This can be rather slow, but I console myself with the
|
||||
thought that people who use \<digit> deserve very slow execution.
|
||||
(Pun unintentional but very appropriate.)
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
|
||||
#ifdef TRE_USE_ALLOCA
|
||||
/* AIX requires this to be the first thing in the file. */
|
||||
#ifndef __GNUC__
|
||||
# if HAVE_ALLOCA_H
|
||||
# include <alloca.h>
|
||||
# else
|
||||
# ifdef _AIX
|
||||
#pragma alloca
|
||||
# else
|
||||
# ifndef alloca /* predefined by HP cc +Olibcalls */
|
||||
char *alloca ();
|
||||
# endif
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
#endif /* TRE_USE_ALLOCA */
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#ifdef HAVE_WCHAR_H
|
||||
#include <wchar.h>
|
||||
#endif /* HAVE_WCHAR_H */
|
||||
#ifdef HAVE_WCTYPE_H
|
||||
#include <wctype.h>
|
||||
#endif /* HAVE_WCTYPE_H */
|
||||
#ifndef TRE_WCHAR
|
||||
#include <ctype.h>
|
||||
#endif /* !TRE_WCHAR */
|
||||
#ifdef HAVE_MALLOC_H
|
||||
#include <malloc.h>
|
||||
#endif /* HAVE_MALLOC_H */
|
||||
|
||||
#include "tre-internal.h"
|
||||
#include "tre-mem.h"
|
||||
#include "tre-match-utils.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
typedef struct {
|
||||
int pos;
|
||||
const char *str_byte;
|
||||
#ifdef TRE_WCHAR
|
||||
const wchar_t *str_wide;
|
||||
#endif /* TRE_WCHAR */
|
||||
tre_tnfa_transition_t *state;
|
||||
int state_id;
|
||||
int next_c;
|
||||
int *tags;
|
||||
#ifdef TRE_MBSTATE
|
||||
mbstate_t mbstate;
|
||||
#endif /* TRE_MBSTATE */
|
||||
} tre_backtrack_item_t;
|
||||
|
||||
typedef struct tre_backtrack_struct {
|
||||
tre_backtrack_item_t item;
|
||||
struct tre_backtrack_struct *prev;
|
||||
struct tre_backtrack_struct *next;
|
||||
} *tre_backtrack_t;
|
||||
|
||||
#ifdef TRE_WCHAR
|
||||
#define BT_STACK_WIDE_IN(_str_wide) stack->item.str_wide = (_str_wide)
|
||||
#define BT_STACK_WIDE_OUT (str_wide) = stack->item.str_wide
|
||||
#else /* !TRE_WCHAR */
|
||||
#define BT_STACK_WIDE_IN(_str_wide)
|
||||
#define BT_STACK_WIDE_OUT
|
||||
#endif /* !TRE_WCHAR */
|
||||
|
||||
#ifdef TRE_MBSTATE
|
||||
#define BT_STACK_MBSTATE_IN stack->item.mbstate = (mbstate)
|
||||
#define BT_STACK_MBSTATE_OUT (mbstate) = stack->item.mbstate
|
||||
#else /* !TRE_MBSTATE */
|
||||
#define BT_STACK_MBSTATE_IN
|
||||
#define BT_STACK_MBSTATE_OUT
|
||||
#endif /* !TRE_MBSTATE */
|
||||
|
||||
|
||||
#ifdef TRE_USE_ALLOCA
|
||||
#define tre_bt_mem_new tre_mem_newa
|
||||
#define tre_bt_mem_alloc tre_mem_alloca
|
||||
#define tre_bt_mem_destroy(obj) do { } while (0)
|
||||
#define xafree(obj) do { } while (0) /* do nothing, obj was obtained with alloca() */
|
||||
#else /* !TRE_USE_ALLOCA */
|
||||
#define tre_bt_mem_new tre_mem_new
|
||||
#define tre_bt_mem_alloc tre_mem_alloc
|
||||
#define tre_bt_mem_destroy tre_mem_destroy
|
||||
#define xafree(obj) xfree(obj)
|
||||
#endif /* !TRE_USE_ALLOCA */
|
||||
|
||||
|
||||
#define BT_STACK_PUSH(_pos, _str_byte, _str_wide, _state, _state_id, _next_c, _tags, _mbstate) \
|
||||
do \
|
||||
{ \
|
||||
int i; \
|
||||
if (!stack->next) \
|
||||
{ \
|
||||
tre_backtrack_t s; \
|
||||
s = tre_bt_mem_alloc(mem, sizeof(*s)); \
|
||||
if (!s) \
|
||||
{ \
|
||||
tre_bt_mem_destroy(mem); \
|
||||
if (tags) \
|
||||
xafree(tags); \
|
||||
if (pmatch) \
|
||||
xafree(pmatch); \
|
||||
if (states_seen) \
|
||||
xafree(states_seen); \
|
||||
return REG_ESPACE; \
|
||||
} \
|
||||
s->prev = stack; \
|
||||
s->next = NULL; \
|
||||
s->item.tags = tre_bt_mem_alloc(mem, \
|
||||
sizeof(*tags) * tnfa->num_tags); \
|
||||
if (!s->item.tags) \
|
||||
{ \
|
||||
tre_bt_mem_destroy(mem); \
|
||||
if (tags) \
|
||||
xafree(tags); \
|
||||
if (pmatch) \
|
||||
xafree(pmatch); \
|
||||
if (states_seen) \
|
||||
xafree(states_seen); \
|
||||
return REG_ESPACE; \
|
||||
} \
|
||||
stack->next = s; \
|
||||
stack = s; \
|
||||
} \
|
||||
else \
|
||||
stack = stack->next; \
|
||||
stack->item.pos = (_pos); \
|
||||
stack->item.str_byte = (_str_byte); \
|
||||
BT_STACK_WIDE_IN(_str_wide); \
|
||||
stack->item.state = (_state); \
|
||||
stack->item.state_id = (_state_id); \
|
||||
stack->item.next_c = (_next_c); \
|
||||
for (i = 0; i < tnfa->num_tags; i++) \
|
||||
stack->item.tags[i] = (_tags)[i]; \
|
||||
BT_STACK_MBSTATE_IN; \
|
||||
} \
|
||||
while (/*CONSTCOND*/(void)0,0)
|
||||
|
||||
#define BT_STACK_POP() \
|
||||
do \
|
||||
{ \
|
||||
int i; \
|
||||
assert(stack->prev); \
|
||||
pos = stack->item.pos; \
|
||||
if (type == STR_USER) \
|
||||
str_source->rewind(pos + pos_add_next, str_source->context); \
|
||||
str_byte = stack->item.str_byte; \
|
||||
BT_STACK_WIDE_OUT; \
|
||||
state = stack->item.state; \
|
||||
next_c = (tre_char_t) stack->item.next_c; \
|
||||
for (i = 0; i < tnfa->num_tags; i++) \
|
||||
tags[i] = stack->item.tags[i]; \
|
||||
BT_STACK_MBSTATE_OUT; \
|
||||
stack = stack->prev; \
|
||||
} \
|
||||
while (/*CONSTCOND*/(void)0,0)
|
||||
|
||||
#undef MIN
|
||||
#define MIN(a, b) ((a) <= (b) ? (a) : (b))
|
||||
|
||||
reg_errcode_t
|
||||
tre_tnfa_run_backtrack(const tre_tnfa_t *tnfa, const void *string,
|
||||
ssize_t len, tre_str_type_t type, int *match_tags,
|
||||
int eflags, int *match_end_ofs)
|
||||
{
|
||||
/* State variables required by GET_NEXT_WCHAR. */
|
||||
tre_char_t prev_c = 0, next_c = 0;
|
||||
const char *str_byte = string;
|
||||
ssize_t pos = 0;
|
||||
unsigned int pos_add_next = 1;
|
||||
#ifdef TRE_WCHAR
|
||||
const wchar_t *str_wide = string;
|
||||
#ifdef TRE_MBSTATE
|
||||
mbstate_t mbstate;
|
||||
#endif /* TRE_MBSTATE */
|
||||
#endif /* TRE_WCHAR */
|
||||
int reg_notbol = eflags & REG_NOTBOL;
|
||||
int reg_noteol = eflags & REG_NOTEOL;
|
||||
int reg_newline = tnfa->cflags & REG_NEWLINE;
|
||||
int str_user_end = 0;
|
||||
|
||||
/* These are used to remember the necessary values of the above
|
||||
variables to return to the position where the current search
|
||||
started from. */
|
||||
int next_c_start;
|
||||
const char *str_byte_start;
|
||||
int pos_start = -1;
|
||||
#ifdef TRE_WCHAR
|
||||
const wchar_t *str_wide_start;
|
||||
#endif /* TRE_WCHAR */
|
||||
#ifdef TRE_MBSTATE
|
||||
mbstate_t mbstate_start;
|
||||
#endif /* TRE_MBSTATE */
|
||||
reg_errcode_t ret;
|
||||
|
||||
/* End offset of best match so far, or -1 if no match found yet. */
|
||||
int match_eo = -1;
|
||||
/* Tag arrays. */
|
||||
int *next_tags, *tags = NULL;
|
||||
/* Current TNFA state. */
|
||||
tre_tnfa_transition_t *state;
|
||||
int *states_seen = NULL;
|
||||
|
||||
/* Memory allocator to for allocating the backtracking stack. */
|
||||
tre_mem_t mem = tre_bt_mem_new();
|
||||
|
||||
/* The backtracking stack. */
|
||||
tre_backtrack_t stack;
|
||||
|
||||
tre_tnfa_transition_t *trans_i;
|
||||
regmatch_t *pmatch = NULL;
|
||||
|
||||
/*
|
||||
* TRE internals tend to use int instead of size_t for positions or
|
||||
* lengths and don't check for overflow. This will take time to fix
|
||||
* properly. In the meantime, simply limit the input to what we can
|
||||
* handle.
|
||||
*/
|
||||
if (len > TRE_MAX_STRING)
|
||||
len = TRE_MAX_STRING;
|
||||
|
||||
#ifdef TRE_MBSTATE
|
||||
memset(&mbstate, '\0', sizeof(mbstate));
|
||||
#endif /* TRE_MBSTATE */
|
||||
|
||||
if (!mem)
|
||||
return REG_ESPACE;
|
||||
stack = tre_bt_mem_alloc(mem, sizeof(*stack));
|
||||
if (!stack)
|
||||
{
|
||||
ret = REG_ESPACE;
|
||||
goto error_exit;
|
||||
}
|
||||
stack->prev = NULL;
|
||||
stack->next = NULL;
|
||||
|
||||
DPRINT(("tnfa_execute_backtrack, input type %d\n", type));
|
||||
DPRINT(("len = %zd\n", len));
|
||||
|
||||
#ifdef TRE_USE_ALLOCA
|
||||
tags = alloca(sizeof(*tags) * tnfa->num_tags);
|
||||
pmatch = alloca(sizeof(*pmatch) * tnfa->num_submatches);
|
||||
states_seen = alloca(sizeof(*states_seen) * tnfa->num_states);
|
||||
#else /* !TRE_USE_ALLOCA */
|
||||
if (tnfa->num_tags)
|
||||
{
|
||||
tags = xmalloc(sizeof(*tags) * tnfa->num_tags);
|
||||
if (!tags)
|
||||
{
|
||||
ret = REG_ESPACE;
|
||||
goto error_exit;
|
||||
}
|
||||
}
|
||||
if (tnfa->num_submatches)
|
||||
{
|
||||
pmatch = xmalloc(sizeof(*pmatch) * tnfa->num_submatches);
|
||||
if (!pmatch)
|
||||
{
|
||||
ret = REG_ESPACE;
|
||||
goto error_exit;
|
||||
}
|
||||
}
|
||||
if (tnfa->num_states)
|
||||
{
|
||||
states_seen = xmalloc(sizeof(*states_seen) * tnfa->num_states);
|
||||
if (!states_seen)
|
||||
{
|
||||
ret = REG_ESPACE;
|
||||
goto error_exit;
|
||||
}
|
||||
}
|
||||
#endif /* !TRE_USE_ALLOCA */
|
||||
|
||||
retry:
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < tnfa->num_tags; i++)
|
||||
{
|
||||
tags[i] = -1;
|
||||
if (match_tags)
|
||||
match_tags[i] = -1;
|
||||
}
|
||||
for (i = 0; i < tnfa->num_states; i++)
|
||||
states_seen[i] = 0;
|
||||
}
|
||||
|
||||
state = NULL;
|
||||
pos = pos_start;
|
||||
if (type == STR_USER)
|
||||
str_source->rewind(pos + pos_add_next, str_source->context);
|
||||
GET_NEXT_WCHAR();
|
||||
pos_start = pos;
|
||||
next_c_start = next_c;
|
||||
str_byte_start = str_byte;
|
||||
#ifdef TRE_WCHAR
|
||||
str_wide_start = str_wide;
|
||||
#endif /* TRE_WCHAR */
|
||||
#ifdef TRE_MBSTATE
|
||||
mbstate_start = mbstate;
|
||||
#endif /* TRE_MBSTATE */
|
||||
|
||||
/* Handle initial states. */
|
||||
next_tags = NULL;
|
||||
for (trans_i = tnfa->initial; trans_i->state; trans_i++)
|
||||
{
|
||||
DPRINT(("> init %p, prev_c %lc\n", trans_i->state, (tre_cint_t)prev_c));
|
||||
if (trans_i->assertions && CHECK_ASSERTIONS(trans_i->assertions))
|
||||
{
|
||||
DPRINT(("assert failed\n"));
|
||||
continue;
|
||||
}
|
||||
if (state == NULL)
|
||||
{
|
||||
/* Start from this state. */
|
||||
state = trans_i->state;
|
||||
next_tags = trans_i->tags;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Backtrack to this state. */
|
||||
DPRINT(("saving state %d for backtracking\n", trans_i->state_id));
|
||||
BT_STACK_PUSH(pos, str_byte, str_wide, trans_i->state,
|
||||
trans_i->state_id, next_c, tags, mbstate);
|
||||
{
|
||||
int *tmp = trans_i->tags;
|
||||
if (tmp)
|
||||
while (*tmp >= 0)
|
||||
stack->item.tags[*tmp++] = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (next_tags)
|
||||
for (; *next_tags >= 0; next_tags++)
|
||||
tags[*next_tags] = pos;
|
||||
|
||||
|
||||
DPRINT(("entering match loop, pos %zd, str_byte %p\n", pos, str_byte));
|
||||
DPRINT(("pos:chr/code | state and tags\n"));
|
||||
DPRINT(("-------------+------------------------------------------------\n"));
|
||||
|
||||
if (state == NULL)
|
||||
goto backtrack;
|
||||
|
||||
while (/*CONSTCOND*/(void)1,1)
|
||||
{
|
||||
tre_tnfa_transition_t *next_state;
|
||||
int empty_br_match;
|
||||
|
||||
DPRINT(("start loop\n"));
|
||||
if (state == tnfa->final)
|
||||
{
|
||||
DPRINT((" match found, %d %zd\n", match_eo, pos));
|
||||
if (match_eo < pos
|
||||
|| (match_eo == pos
|
||||
&& match_tags
|
||||
&& tre_tag_order(tnfa->num_tags, tnfa->tag_directions,
|
||||
tags, match_tags)))
|
||||
{
|
||||
int i;
|
||||
/* This match wins the previous match. */
|
||||
DPRINT((" win previous\n"));
|
||||
match_eo = pos;
|
||||
if (match_tags)
|
||||
for (i = 0; i < tnfa->num_tags; i++)
|
||||
match_tags[i] = tags[i];
|
||||
}
|
||||
/* Our TNFAs never have transitions leaving from the final state,
|
||||
so we jump right to backtracking. */
|
||||
goto backtrack;
|
||||
}
|
||||
|
||||
#ifdef TRE_DEBUG
|
||||
DPRINT(("%3zd:%2lc/%05d | %p ", pos, (tre_cint_t)next_c, (int)next_c,
|
||||
state));
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < tnfa->num_tags; i++)
|
||||
DPRINT(("%d%s", tags[i], i < tnfa->num_tags - 1 ? ", " : ""));
|
||||
DPRINT(("\n"));
|
||||
}
|
||||
#endif /* TRE_DEBUG */
|
||||
|
||||
/* Go to the next character in the input string. */
|
||||
empty_br_match = 0;
|
||||
trans_i = state;
|
||||
if (trans_i->state && trans_i->assertions & ASSERT_BACKREF)
|
||||
{
|
||||
/* This is a back reference state. All transitions leaving from
|
||||
this state have the same back reference "assertion". Instead
|
||||
of reading the next character, we match the back reference. */
|
||||
int so, eo, bt = trans_i->u.backref;
|
||||
int bt_len;
|
||||
int result;
|
||||
|
||||
DPRINT((" should match back reference %d\n", bt));
|
||||
/* Get the substring we need to match against. Remember to
|
||||
turn off REG_NOSUB temporarily. */
|
||||
tre_fill_pmatch(bt + 1, pmatch, tnfa->cflags & ~REG_NOSUB,
|
||||
tnfa, tags, pos);
|
||||
so = pmatch[bt].rm_so;
|
||||
eo = pmatch[bt].rm_eo;
|
||||
bt_len = eo - so;
|
||||
|
||||
#ifdef TRE_DEBUG
|
||||
{
|
||||
int slen;
|
||||
if (len < 0)
|
||||
slen = bt_len;
|
||||
else
|
||||
slen = MIN(bt_len, len - pos);
|
||||
|
||||
if (type == STR_BYTE)
|
||||
{
|
||||
DPRINT((" substring (len %d) is [%d, %d[: '%.*s'\n",
|
||||
bt_len, so, eo, bt_len, (char*)string + so));
|
||||
DPRINT((" current string is '%.*s'\n", slen, str_byte - 1));
|
||||
}
|
||||
#ifdef TRE_WCHAR
|
||||
else if (type == STR_WIDE)
|
||||
{
|
||||
DPRINT((" substring (len %d) is [%d, %d[: '%.*" STRF "'\n",
|
||||
bt_len, so, eo, bt_len, (wchar_t*)string + so));
|
||||
DPRINT((" current string is '%.*" STRF "'\n",
|
||||
slen, str_wide - 1));
|
||||
}
|
||||
#endif /* TRE_WCHAR */
|
||||
}
|
||||
#endif
|
||||
|
||||
if (len < 0)
|
||||
{
|
||||
if (type == STR_USER)
|
||||
result = str_source->compare((unsigned)so, (unsigned)pos,
|
||||
(unsigned)bt_len,
|
||||
str_source->context);
|
||||
#ifdef TRE_WCHAR
|
||||
else if (type == STR_WIDE)
|
||||
result = wcsncmp((const wchar_t*)string + so, str_wide - 1,
|
||||
(size_t)bt_len);
|
||||
#endif /* TRE_WCHAR */
|
||||
else
|
||||
result = strncmp((const char*)string + so, str_byte - 1,
|
||||
(size_t)bt_len);
|
||||
}
|
||||
else if (len - pos < bt_len)
|
||||
result = 1;
|
||||
#ifdef TRE_WCHAR
|
||||
else if (type == STR_WIDE)
|
||||
result = wmemcmp((const wchar_t*)string + so, str_wide - 1,
|
||||
(size_t)bt_len);
|
||||
#endif /* TRE_WCHAR */
|
||||
else
|
||||
result = memcmp((const char*)string + so, str_byte - 1,
|
||||
(size_t)bt_len);
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
/* Back reference matched. Check for infinite loop. */
|
||||
if (bt_len == 0)
|
||||
empty_br_match = 1;
|
||||
if (empty_br_match && states_seen[trans_i->state_id])
|
||||
{
|
||||
DPRINT((" avoid loop\n"));
|
||||
goto backtrack;
|
||||
}
|
||||
|
||||
states_seen[trans_i->state_id] = empty_br_match;
|
||||
|
||||
/* Advance in input string and resync `prev_c', `next_c'
|
||||
and pos. */
|
||||
DPRINT((" back reference matched\n"));
|
||||
str_byte += bt_len - 1;
|
||||
#ifdef TRE_WCHAR
|
||||
str_wide += bt_len - 1;
|
||||
#endif /* TRE_WCHAR */
|
||||
pos += bt_len - 1;
|
||||
GET_NEXT_WCHAR();
|
||||
DPRINT((" pos now %zd\n", pos));
|
||||
}
|
||||
else
|
||||
{
|
||||
DPRINT((" back reference did not match\n"));
|
||||
goto backtrack;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Check for end of string. */
|
||||
if (len < 0)
|
||||
{
|
||||
if (type == STR_USER)
|
||||
{
|
||||
if (str_user_end)
|
||||
goto backtrack;
|
||||
}
|
||||
else if (next_c == L'\0' || pos >= TRE_MAX_STRING)
|
||||
goto backtrack;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pos >= len)
|
||||
goto backtrack;
|
||||
}
|
||||
|
||||
/* Read the next character. */
|
||||
GET_NEXT_WCHAR();
|
||||
}
|
||||
|
||||
next_state = NULL;
|
||||
for (trans_i = state; trans_i->state; trans_i++)
|
||||
{
|
||||
DPRINT((" transition %d-%d (%c-%c) %d to %d\n",
|
||||
trans_i->code_min, trans_i->code_max,
|
||||
trans_i->code_min, trans_i->code_max,
|
||||
trans_i->assertions, trans_i->state_id));
|
||||
if (trans_i->code_min <= (tre_cint_t)prev_c
|
||||
&& trans_i->code_max >= (tre_cint_t)prev_c)
|
||||
{
|
||||
if (trans_i->assertions
|
||||
&& (CHECK_ASSERTIONS(trans_i->assertions)
|
||||
|| CHECK_CHAR_CLASSES(trans_i, tnfa, eflags)))
|
||||
{
|
||||
DPRINT((" assertion failed\n"));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (next_state == NULL)
|
||||
{
|
||||
/* First matching transition. */
|
||||
DPRINT((" Next state is %d\n", trans_i->state_id));
|
||||
next_state = trans_i->state;
|
||||
next_tags = trans_i->tags;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Second matching transition. We may need to backtrack here
|
||||
to take this transition instead of the first one, so we
|
||||
push this transition in the backtracking stack so we can
|
||||
jump back here if needed. */
|
||||
DPRINT((" saving state %d for backtracking\n",
|
||||
trans_i->state_id));
|
||||
BT_STACK_PUSH(pos, str_byte, str_wide, trans_i->state,
|
||||
trans_i->state_id, next_c, tags, mbstate);
|
||||
{
|
||||
int *tmp;
|
||||
for (tmp = trans_i->tags; tmp && *tmp >= 0; tmp++)
|
||||
stack->item.tags[*tmp] = pos;
|
||||
}
|
||||
#if 0 /* XXX - it's important not to look at all transitions here to keep
|
||||
the stack small! */
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (next_state != NULL)
|
||||
{
|
||||
/* Matching transitions were found. Take the first one. */
|
||||
state = next_state;
|
||||
|
||||
/* Update the tag values. */
|
||||
if (next_tags)
|
||||
while (*next_tags >= 0)
|
||||
tags[*next_tags++] = pos;
|
||||
}
|
||||
else
|
||||
{
|
||||
backtrack:
|
||||
/* A matching transition was not found. Try to backtrack. */
|
||||
if (stack->prev)
|
||||
{
|
||||
DPRINT((" backtracking\n"));
|
||||
if (stack->item.state->assertions & ASSERT_BACKREF)
|
||||
{
|
||||
DPRINT((" states_seen[%d] = 0\n",
|
||||
stack->item.state_id));
|
||||
states_seen[stack->item.state_id] = 0;
|
||||
}
|
||||
|
||||
BT_STACK_POP();
|
||||
}
|
||||
else if (match_eo < 0)
|
||||
{
|
||||
/* Try starting from a later position in the input string. */
|
||||
/* Check for end of string. */
|
||||
if (len < 0)
|
||||
{
|
||||
if (next_c_start == L'\0' || pos_start >= TRE_MAX_STRING)
|
||||
{
|
||||
DPRINT(("end of string.\n"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pos_start >= len)
|
||||
{
|
||||
DPRINT(("end of string.\n"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
DPRINT(("restarting from next start position\n"));
|
||||
next_c = (tre_char_t) next_c_start;
|
||||
#ifdef TRE_MBSTATE
|
||||
mbstate = mbstate_start;
|
||||
#endif /* TRE_MBSTATE */
|
||||
str_byte = str_byte_start;
|
||||
#ifdef TRE_WCHAR
|
||||
str_wide = str_wide_start;
|
||||
#endif /* TRE_WCHAR */
|
||||
goto retry;
|
||||
}
|
||||
else
|
||||
{
|
||||
DPRINT(("finished\n"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret = match_eo >= 0 ? REG_OK : REG_NOMATCH;
|
||||
*match_end_ofs = match_eo;
|
||||
|
||||
error_exit:
|
||||
tre_bt_mem_destroy(mem);
|
||||
#ifndef TRE_USE_ALLOCA
|
||||
if (tags)
|
||||
xafree(tags);
|
||||
if (pmatch)
|
||||
xafree(pmatch);
|
||||
if (states_seen)
|
||||
xafree(states_seen);
|
||||
#endif /* !TRE_USE_ALLOCA */
|
||||
|
||||
return ret;
|
||||
}
|
||||
538
deps/tre/lib/tre-match-parallel.c
vendored
Normal file
538
deps/tre/lib/tre-match-parallel.c
vendored
Normal file
|
|
@ -0,0 +1,538 @@
|
|||
/*
|
||||
tre-match-parallel.c - TRE parallel regex matching engine
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
This algorithm searches for matches basically by reading characters
|
||||
in the searched string one by one, starting at the beginning. All
|
||||
matching paths in the TNFA are traversed in parallel. When two or
|
||||
more paths reach the same state, exactly one is chosen according to
|
||||
tag ordering rules; if returning submatches is not required it does
|
||||
not matter which path is chosen.
|
||||
|
||||
The worst case time required for finding the leftmost and longest
|
||||
match, or determining that there is no match, is always linearly
|
||||
dependent on the length of the text being searched.
|
||||
|
||||
This algorithm cannot handle TNFAs with back referencing nodes.
|
||||
See `tre-match-backtrack.c'.
|
||||
*/
|
||||
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
|
||||
#ifdef TRE_USE_ALLOCA
|
||||
/* AIX requires this to be the first thing in the file. */
|
||||
#ifndef __GNUC__
|
||||
# if HAVE_ALLOCA_H
|
||||
# include <alloca.h>
|
||||
# else
|
||||
# ifdef _AIX
|
||||
#pragma alloca
|
||||
# else
|
||||
# ifndef alloca /* predefined by HP cc +Olibcalls */
|
||||
char *alloca ();
|
||||
# endif
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
#endif /* TRE_USE_ALLOCA */
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#ifdef HAVE_WCHAR_H
|
||||
#include <wchar.h>
|
||||
#endif /* HAVE_WCHAR_H */
|
||||
#ifdef HAVE_WCTYPE_H
|
||||
#include <wctype.h>
|
||||
#endif /* HAVE_WCTYPE_H */
|
||||
#ifndef TRE_WCHAR
|
||||
#include <ctype.h>
|
||||
#endif /* !TRE_WCHAR */
|
||||
#ifdef HAVE_MALLOC_H
|
||||
#include <malloc.h>
|
||||
#endif /* HAVE_MALLOC_H */
|
||||
|
||||
#include "tre-internal.h"
|
||||
#include "tre-match-utils.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
|
||||
|
||||
typedef struct {
|
||||
tre_tnfa_transition_t *state;
|
||||
int *tags;
|
||||
} tre_tnfa_reach_t;
|
||||
|
||||
typedef struct {
|
||||
int pos;
|
||||
int **tags;
|
||||
} tre_reach_pos_t;
|
||||
|
||||
|
||||
#ifdef TRE_DEBUG
|
||||
static void
|
||||
tre_print_reach(const tre_tnfa_reach_t *reach, int num_tags)
|
||||
{
|
||||
int i;
|
||||
|
||||
while (reach->state != NULL)
|
||||
{
|
||||
DPRINT((" %p", (void *)reach->state));
|
||||
if (num_tags > 0)
|
||||
{
|
||||
DPRINT(("/"));
|
||||
for (i = 0; i < num_tags; i++)
|
||||
{
|
||||
DPRINT(("%d:%d", i, reach->tags[i]));
|
||||
if (i < (num_tags-1))
|
||||
DPRINT((","));
|
||||
}
|
||||
}
|
||||
reach++;
|
||||
}
|
||||
DPRINT(("\n"));
|
||||
|
||||
}
|
||||
#endif /* TRE_DEBUG */
|
||||
|
||||
reg_errcode_t
|
||||
tre_tnfa_run_parallel(const tre_tnfa_t *tnfa, const void *string, ssize_t len,
|
||||
tre_str_type_t type, int *match_tags, int eflags,
|
||||
int *match_end_ofs)
|
||||
{
|
||||
/* State variables required by GET_NEXT_WCHAR. */
|
||||
tre_char_t prev_c = 0, next_c = 0;
|
||||
const char *str_byte = string;
|
||||
ssize_t pos = -1;
|
||||
unsigned int pos_add_next = 1;
|
||||
#ifdef TRE_WCHAR
|
||||
const wchar_t *str_wide = string;
|
||||
#ifdef TRE_MBSTATE
|
||||
mbstate_t mbstate;
|
||||
#endif /* TRE_MBSTATE */
|
||||
#endif /* TRE_WCHAR */
|
||||
reg_errcode_t ret;
|
||||
int reg_notbol = eflags & REG_NOTBOL;
|
||||
int reg_noteol = eflags & REG_NOTEOL;
|
||||
int reg_newline = tnfa->cflags & REG_NEWLINE;
|
||||
int str_user_end = 0;
|
||||
|
||||
char *buf;
|
||||
tre_tnfa_transition_t *trans_i;
|
||||
tre_tnfa_reach_t *reach, *reach_next, *reach_i, *reach_next_i;
|
||||
tre_reach_pos_t *reach_pos;
|
||||
int *tag_i;
|
||||
int num_tags, i;
|
||||
|
||||
int match_eo = -1; /* end offset of match (-1 if no match found yet) */
|
||||
int new_match = 0;
|
||||
int *tmp_tags = NULL;
|
||||
int *tmp_iptr;
|
||||
|
||||
/*
|
||||
* TRE internals tend to use int instead of size_t for positions or
|
||||
* lengths and don't check for overflow. This will take time to fix
|
||||
* properly. In the meantime, simply limit the input to what we can
|
||||
* handle.
|
||||
*/
|
||||
if (len > TRE_MAX_STRING)
|
||||
len = TRE_MAX_STRING;
|
||||
|
||||
#ifdef TRE_MBSTATE
|
||||
memset(&mbstate, '\0', sizeof(mbstate));
|
||||
#endif /* TRE_MBSTATE */
|
||||
|
||||
DPRINT(("tre_tnfa_run_parallel, input type %d\n", type));
|
||||
|
||||
if (!match_tags)
|
||||
num_tags = 0;
|
||||
else
|
||||
num_tags = tnfa->num_tags;
|
||||
|
||||
/* Allocate memory for temporary data required for matching. This needs to
|
||||
be done for every matching operation to be thread safe. This allocates
|
||||
everything in a single large block from the stack frame using alloca()
|
||||
or with malloc() if alloca is unavailable. */
|
||||
{
|
||||
size_t tbytes, rbytes, pbytes, xbytes, total_bytes;
|
||||
size_t num_states = (size_t)tnfa->num_states;
|
||||
size_t state_tag_bytes, reach_bytes;
|
||||
size_t padding = (sizeof(long) - 1) * 4;
|
||||
char *tmp_buf;
|
||||
|
||||
if (num_states > SIZE_MAX / sizeof(*reach_pos))
|
||||
return REG_ESPACE;
|
||||
pbytes = sizeof(*reach_pos) * num_states;
|
||||
|
||||
if (num_states + 1 > SIZE_MAX / sizeof(*reach_next))
|
||||
return REG_ESPACE;
|
||||
rbytes = sizeof(*reach_next) * (num_states + 1);
|
||||
|
||||
if ((size_t)num_tags > SIZE_MAX / sizeof(*tmp_tags))
|
||||
return REG_ESPACE;
|
||||
tbytes = sizeof(*tmp_tags) * (size_t)num_tags;
|
||||
|
||||
if ((size_t)num_tags > SIZE_MAX / sizeof(int))
|
||||
return REG_ESPACE;
|
||||
xbytes = sizeof(int) * (size_t)num_tags;
|
||||
|
||||
if (num_states > 0 && xbytes > SIZE_MAX / num_states)
|
||||
return REG_ESPACE;
|
||||
state_tag_bytes = xbytes * num_states;
|
||||
|
||||
if (rbytes > SIZE_MAX - state_tag_bytes)
|
||||
return REG_ESPACE;
|
||||
reach_bytes = rbytes + state_tag_bytes;
|
||||
|
||||
if (reach_bytes > (SIZE_MAX - padding - tbytes - pbytes) / 2)
|
||||
return REG_ESPACE;
|
||||
|
||||
/* Compute the length of the block we need. */
|
||||
total_bytes =
|
||||
padding + reach_bytes * 2 + tbytes + pbytes;
|
||||
|
||||
/* Allocate the memory. */
|
||||
#ifdef TRE_USE_ALLOCA
|
||||
buf = alloca(total_bytes);
|
||||
#else /* !TRE_USE_ALLOCA */
|
||||
buf = xmalloc(total_bytes);
|
||||
#endif /* !TRE_USE_ALLOCA */
|
||||
if (buf == NULL)
|
||||
return REG_ESPACE;
|
||||
memset(buf, 0, total_bytes);
|
||||
|
||||
/* Get the various pointers within tmp_buf (properly aligned). */
|
||||
tmp_tags = (void *)buf;
|
||||
tmp_buf = buf + tbytes;
|
||||
tmp_buf += ALIGN(tmp_buf, long);
|
||||
reach_next = (void *)tmp_buf;
|
||||
tmp_buf += rbytes;
|
||||
tmp_buf += ALIGN(tmp_buf, long);
|
||||
reach = (void *)tmp_buf;
|
||||
tmp_buf += rbytes;
|
||||
tmp_buf += ALIGN(tmp_buf, long);
|
||||
reach_pos = (void *)tmp_buf;
|
||||
tmp_buf += pbytes;
|
||||
tmp_buf += ALIGN(tmp_buf, long);
|
||||
for (i = 0; i < tnfa->num_states; i++)
|
||||
{
|
||||
reach[i].tags = (void *)tmp_buf;
|
||||
tmp_buf += xbytes;
|
||||
reach_next[i].tags = (void *)tmp_buf;
|
||||
tmp_buf += xbytes;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < tnfa->num_states; i++)
|
||||
reach_pos[i].pos = -1;
|
||||
|
||||
/* If only one character can start a match, find it first. */
|
||||
if (tnfa->first_char >= 0 && type == STR_BYTE && str_byte)
|
||||
{
|
||||
const char *orig_str = str_byte;
|
||||
int first = tnfa->first_char;
|
||||
|
||||
if (len >= 0)
|
||||
str_byte = memchr(orig_str, first, (size_t)len);
|
||||
else
|
||||
str_byte = strchr(orig_str, first);
|
||||
if (str_byte == NULL)
|
||||
{
|
||||
#ifndef TRE_USE_ALLOCA
|
||||
if (buf)
|
||||
xfree(buf);
|
||||
#endif /* !TRE_USE_ALLOCA */
|
||||
return REG_NOMATCH;
|
||||
}
|
||||
DPRINT(("skipped %lu chars\n", (unsigned long)(str_byte - orig_str)));
|
||||
if (str_byte >= orig_str + 1)
|
||||
prev_c = (unsigned char)*(str_byte - 1);
|
||||
next_c = (unsigned char)*str_byte;
|
||||
pos = str_byte - orig_str;
|
||||
if (len < 0 || pos < len)
|
||||
str_byte++;
|
||||
}
|
||||
else
|
||||
{
|
||||
GET_NEXT_WCHAR();
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
#if 0
|
||||
/* Skip over characters that cannot possibly be the first character
|
||||
of a match. */
|
||||
if (tnfa->firstpos_chars != NULL)
|
||||
{
|
||||
char *chars = tnfa->firstpos_chars;
|
||||
|
||||
if (len < 0)
|
||||
{
|
||||
const char *orig_str = str_byte;
|
||||
/* XXX - use strpbrk() and wcspbrk() because they might be
|
||||
optimized for the target architecture. Try also strcspn()
|
||||
and wcscspn() and compare the speeds. */
|
||||
while (next_c != L'\0' && !chars[next_c])
|
||||
{
|
||||
next_c = *str_byte++;
|
||||
}
|
||||
prev_c = *(str_byte - 2);
|
||||
pos += str_byte - orig_str;
|
||||
DPRINT(("skipped %d chars\n", str_byte - orig_str));
|
||||
}
|
||||
else
|
||||
{
|
||||
while (pos <= len && !chars[next_c])
|
||||
{
|
||||
prev_c = next_c;
|
||||
next_c = (unsigned char)(*str_byte++);
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
DPRINT(("length: %zd\n", len));
|
||||
DPRINT(("pos:chr/code | states and tags\n"));
|
||||
DPRINT(("-------------+------------------------------------------------\n"));
|
||||
|
||||
reach_next_i = reach_next;
|
||||
while (/*CONSTCOND*/(void)1,1)
|
||||
{
|
||||
/* If no match found yet, add the initial states to `reach_next'. */
|
||||
if (match_eo < 0)
|
||||
{
|
||||
DPRINT((" init >"));
|
||||
trans_i = tnfa->initial;
|
||||
while (trans_i->state != NULL)
|
||||
{
|
||||
if (reach_pos[trans_i->state_id].pos < pos)
|
||||
{
|
||||
if (trans_i->assertions
|
||||
&& CHECK_ASSERTIONS(trans_i->assertions))
|
||||
{
|
||||
DPRINT(("assertion failed\n"));
|
||||
trans_i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
DPRINT((" %p", (void *)trans_i->state));
|
||||
reach_next_i->state = trans_i->state;
|
||||
for (i = 0; i < num_tags; i++)
|
||||
reach_next_i->tags[i] = -1;
|
||||
tag_i = trans_i->tags;
|
||||
if (tag_i)
|
||||
while (*tag_i >= 0)
|
||||
{
|
||||
if (*tag_i < num_tags)
|
||||
reach_next_i->tags[*tag_i] = pos;
|
||||
tag_i++;
|
||||
}
|
||||
if (reach_next_i->state == tnfa->final)
|
||||
{
|
||||
DPRINT((" found empty match\n"));
|
||||
match_eo = pos;
|
||||
new_match = 1;
|
||||
for (i = 0; i < num_tags; i++)
|
||||
match_tags[i] = reach_next_i->tags[i];
|
||||
}
|
||||
reach_pos[trans_i->state_id].pos = pos;
|
||||
reach_pos[trans_i->state_id].tags = &reach_next_i->tags;
|
||||
reach_next_i++;
|
||||
}
|
||||
trans_i++;
|
||||
}
|
||||
DPRINT(("\n"));
|
||||
reach_next_i->state = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (num_tags == 0 || reach_next_i == reach_next)
|
||||
/* We have found a match. */
|
||||
break;
|
||||
}
|
||||
|
||||
/* Check for end of string. */
|
||||
if (len < 0)
|
||||
{
|
||||
if (type == STR_USER)
|
||||
{
|
||||
if (str_user_end)
|
||||
break;
|
||||
}
|
||||
else if (next_c == L'\0' || pos >= TRE_MAX_STRING)
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pos >= len)
|
||||
break;
|
||||
}
|
||||
|
||||
GET_NEXT_WCHAR();
|
||||
|
||||
#ifdef TRE_DEBUG
|
||||
DPRINT(("%3zd:%2lc/%05d |", pos - 1, (tre_cint_t)prev_c, (int)prev_c));
|
||||
tre_print_reach(reach_next, num_tags);
|
||||
DPRINT(("%3zd:%2lc/%05d |", pos, (tre_cint_t)next_c, (int)next_c));
|
||||
tre_print_reach(reach_next, num_tags);
|
||||
#endif /* TRE_DEBUG */
|
||||
|
||||
/* Swap `reach' and `reach_next'. */
|
||||
reach_i = reach;
|
||||
reach = reach_next;
|
||||
reach_next = reach_i;
|
||||
|
||||
/* For each state in `reach', weed out states that don't fulfill the
|
||||
minimal matching conditions. */
|
||||
if (tnfa->num_minimals && new_match)
|
||||
{
|
||||
new_match = 0;
|
||||
reach_next_i = reach_next;
|
||||
for (reach_i = reach; reach_i->state; reach_i++)
|
||||
{
|
||||
int skip = 0;
|
||||
for (i = 0; tnfa->minimal_tags[i] >= 0; i += 2)
|
||||
{
|
||||
int end = tnfa->minimal_tags[i];
|
||||
int start = tnfa->minimal_tags[i + 1];
|
||||
DPRINT((" Minimal start %d, end %d\n", start, end));
|
||||
if (end >= num_tags)
|
||||
{
|
||||
DPRINT((" Throwing %p out.\n", reach_i->state));
|
||||
skip = 1;
|
||||
break;
|
||||
}
|
||||
else if (reach_i->tags[start] == match_tags[start]
|
||||
&& reach_i->tags[end] < match_tags[end])
|
||||
{
|
||||
DPRINT((" Throwing %p out because t%d < %d\n",
|
||||
reach_i->state, end, match_tags[end]));
|
||||
skip = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!skip)
|
||||
{
|
||||
reach_next_i->state = reach_i->state;
|
||||
tmp_iptr = reach_next_i->tags;
|
||||
reach_next_i->tags = reach_i->tags;
|
||||
reach_i->tags = tmp_iptr;
|
||||
reach_next_i++;
|
||||
}
|
||||
}
|
||||
reach_next_i->state = NULL;
|
||||
|
||||
/* Swap `reach' and `reach_next'. */
|
||||
reach_i = reach;
|
||||
reach = reach_next;
|
||||
reach_next = reach_i;
|
||||
}
|
||||
|
||||
/* For each state in `reach' see if there is a transition leaving with
|
||||
the current input symbol to a state not yet in `reach_next', and
|
||||
add the destination states to `reach_next'. */
|
||||
reach_next_i = reach_next;
|
||||
for (reach_i = reach; reach_i->state; reach_i++)
|
||||
{
|
||||
for (trans_i = reach_i->state; trans_i->state; trans_i++)
|
||||
{
|
||||
/* Does this transition match the input symbol? */
|
||||
if (trans_i->code_min <= (tre_cint_t)prev_c &&
|
||||
trans_i->code_max >= (tre_cint_t)prev_c)
|
||||
{
|
||||
if (trans_i->assertions
|
||||
&& (CHECK_ASSERTIONS(trans_i->assertions)
|
||||
|| CHECK_CHAR_CLASSES(trans_i, tnfa, eflags)))
|
||||
{
|
||||
DPRINT(("assertion failed\n"));
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Compute the tags after this transition. */
|
||||
for (i = 0; i < num_tags; i++)
|
||||
tmp_tags[i] = reach_i->tags[i];
|
||||
tag_i = trans_i->tags;
|
||||
if (tag_i != NULL)
|
||||
while (*tag_i >= 0)
|
||||
{
|
||||
if (*tag_i < num_tags)
|
||||
tmp_tags[*tag_i] = pos;
|
||||
tag_i++;
|
||||
}
|
||||
|
||||
if (reach_pos[trans_i->state_id].pos < pos)
|
||||
{
|
||||
/* Found an unvisited node. */
|
||||
reach_next_i->state = trans_i->state;
|
||||
tmp_iptr = reach_next_i->tags;
|
||||
reach_next_i->tags = tmp_tags;
|
||||
tmp_tags = tmp_iptr;
|
||||
reach_pos[trans_i->state_id].pos = pos;
|
||||
reach_pos[trans_i->state_id].tags = &reach_next_i->tags;
|
||||
|
||||
if (reach_next_i->state == tnfa->final
|
||||
&& (match_eo == -1
|
||||
|| (num_tags > 0
|
||||
&& reach_next_i->tags[0] <= match_tags[0])))
|
||||
{
|
||||
DPRINT((" found match %p\n", trans_i->state));
|
||||
match_eo = pos;
|
||||
new_match = 1;
|
||||
for (i = 0; i < num_tags; i++)
|
||||
match_tags[i] = reach_next_i->tags[i];
|
||||
}
|
||||
reach_next_i++;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(reach_pos[trans_i->state_id].pos == pos);
|
||||
/* Another path has also reached this state. We choose
|
||||
the winner by examining the tag values for both
|
||||
paths. */
|
||||
if (tre_tag_order(num_tags, tnfa->tag_directions,
|
||||
tmp_tags,
|
||||
*reach_pos[trans_i->state_id].tags))
|
||||
{
|
||||
/* The new path wins. */
|
||||
tmp_iptr = *reach_pos[trans_i->state_id].tags;
|
||||
*reach_pos[trans_i->state_id].tags = tmp_tags;
|
||||
if (trans_i->state == tnfa->final)
|
||||
{
|
||||
DPRINT((" found better match\n"));
|
||||
match_eo = pos;
|
||||
new_match = 1;
|
||||
for (i = 0; i < num_tags; i++)
|
||||
match_tags[i] = tmp_tags[i];
|
||||
}
|
||||
tmp_tags = tmp_iptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
reach_next_i->state = NULL;
|
||||
}
|
||||
|
||||
DPRINT(("match end offset = %d\n", match_eo));
|
||||
|
||||
*match_end_ofs = match_eo;
|
||||
ret = match_eo >= 0 ? REG_OK : REG_NOMATCH;
|
||||
|
||||
#ifndef TRE_USE_ALLOCA
|
||||
if (buf)
|
||||
xfree(buf);
|
||||
#endif /* !TRE_USE_ALLOCA */
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* EOF */
|
||||
215
deps/tre/lib/tre-match-utils.h
vendored
Normal file
215
deps/tre/lib/tre-match-utils.h
vendored
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
/*
|
||||
tre-match-utils.h - TRE matcher helper definitions
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
#define str_source ((const tre_str_source*)string)
|
||||
|
||||
#ifdef TRE_WCHAR
|
||||
|
||||
#ifdef TRE_MULTIBYTE
|
||||
|
||||
/* Wide character and multibyte support. */
|
||||
|
||||
#define GET_NEXT_WCHAR() \
|
||||
do { \
|
||||
prev_c = next_c; \
|
||||
if (type == STR_BYTE) \
|
||||
{ \
|
||||
pos++; \
|
||||
if (len >= 0 && pos >= len) \
|
||||
next_c = '\0'; \
|
||||
else \
|
||||
next_c = (unsigned char)(*str_byte++); \
|
||||
} \
|
||||
else if (type == STR_WIDE) \
|
||||
{ \
|
||||
pos++; \
|
||||
if (len >= 0 && pos >= len) \
|
||||
next_c = L'\0'; \
|
||||
else \
|
||||
next_c = *str_wide++; \
|
||||
} \
|
||||
else if (type == STR_MBS) \
|
||||
{ \
|
||||
pos += pos_add_next; \
|
||||
if (str_byte == NULL) \
|
||||
next_c = L'\0'; \
|
||||
else \
|
||||
{ \
|
||||
size_t w; \
|
||||
size_t max; \
|
||||
if (len >= 0) \
|
||||
max = len - pos; \
|
||||
else \
|
||||
max = 32; \
|
||||
if (max <= 0) \
|
||||
{ \
|
||||
next_c = L'\0'; \
|
||||
pos_add_next = 1; \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
w = tre_mbrtowc(&next_c, str_byte, (size_t)max, &mbstate); \
|
||||
if (w == (size_t)-1 || w == (size_t)-2) \
|
||||
return REG_NOMATCH; \
|
||||
if (w == 0 && len >= 0) \
|
||||
{ \
|
||||
pos_add_next = 1; \
|
||||
next_c = 0; \
|
||||
str_byte++; \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
pos_add_next = w; \
|
||||
str_byte += w; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
else if (type == STR_USER) \
|
||||
{ \
|
||||
pos += pos_add_next; \
|
||||
str_user_end = str_source->get_next_char(&next_c, &pos_add_next, \
|
||||
str_source->context); \
|
||||
} \
|
||||
} while(/*CONSTCOND*/(void)0,0)
|
||||
|
||||
#else /* !TRE_MULTIBYTE */
|
||||
|
||||
/* Wide character support, no multibyte support. */
|
||||
|
||||
#define GET_NEXT_WCHAR() \
|
||||
do { \
|
||||
prev_c = next_c; \
|
||||
if (type == STR_BYTE) \
|
||||
{ \
|
||||
pos++; \
|
||||
if (len >= 0 && pos >= len) \
|
||||
next_c = '\0'; \
|
||||
else \
|
||||
next_c = (unsigned char)(*str_byte++); \
|
||||
} \
|
||||
else if (type == STR_WIDE) \
|
||||
{ \
|
||||
pos++; \
|
||||
if (len >= 0 && pos >= len) \
|
||||
next_c = L'\0'; \
|
||||
else \
|
||||
next_c = *str_wide++; \
|
||||
} \
|
||||
else if (type == STR_USER) \
|
||||
{ \
|
||||
pos += pos_add_next; \
|
||||
str_user_end = str_source->get_next_char(&next_c, &pos_add_next, \
|
||||
str_source->context); \
|
||||
} \
|
||||
} while(/*CONSTCOND*/(void)0,0)
|
||||
|
||||
#endif /* !TRE_MULTIBYTE */
|
||||
|
||||
#else /* !TRE_WCHAR */
|
||||
|
||||
/* No wide character or multibyte support. */
|
||||
|
||||
#define GET_NEXT_WCHAR() \
|
||||
do { \
|
||||
prev_c = next_c; \
|
||||
if (type == STR_BYTE) \
|
||||
{ \
|
||||
pos++; \
|
||||
if (len >= 0 && pos >= len) \
|
||||
next_c = '\0'; \
|
||||
else \
|
||||
next_c = (unsigned char)(*str_byte++); \
|
||||
} \
|
||||
else if (type == STR_USER) \
|
||||
{ \
|
||||
pos += pos_add_next; \
|
||||
str_user_end = str_source->get_next_char(&next_c, &pos_add_next, \
|
||||
str_source->context); \
|
||||
} \
|
||||
} while(/*CONSTCOND*/(void)0,0)
|
||||
|
||||
#endif /* !TRE_WCHAR */
|
||||
|
||||
|
||||
|
||||
#define IS_WORD_CHAR(c) ((c) == L'_' || tre_isalnum(c))
|
||||
|
||||
#define CHECK_ASSERTIONS(assertions) \
|
||||
(((assertions & ASSERT_AT_BOL) \
|
||||
&& (pos > 0 || reg_notbol) \
|
||||
&& (prev_c != L'\n' || !reg_newline)) \
|
||||
|| ((assertions & ASSERT_AT_EOL) \
|
||||
&& (next_c != L'\0' || reg_noteol) \
|
||||
&& (next_c != L'\n' || !reg_newline)) \
|
||||
|| ((assertions & ASSERT_AT_BOW) \
|
||||
&& (IS_WORD_CHAR(prev_c) || !IS_WORD_CHAR(next_c))) \
|
||||
|| ((assertions & ASSERT_AT_EOW) \
|
||||
&& (!IS_WORD_CHAR(prev_c) || IS_WORD_CHAR(next_c))) \
|
||||
|| ((assertions & ASSERT_AT_WB) \
|
||||
&& (pos != 0 && next_c != L'\0' \
|
||||
&& IS_WORD_CHAR(prev_c) == IS_WORD_CHAR(next_c))) \
|
||||
|| ((assertions & ASSERT_AT_WB_NEG) \
|
||||
&& (pos == 0 || next_c == L'\0' \
|
||||
|| IS_WORD_CHAR(prev_c) != IS_WORD_CHAR(next_c))))
|
||||
|
||||
#define CHECK_CHAR_CLASSES(trans_i, tnfa, eflags) \
|
||||
(((trans_i->assertions & ASSERT_CHAR_CLASS) \
|
||||
&& !(tnfa->cflags & REG_ICASE) \
|
||||
&& !tre_isctype((tre_cint_t)prev_c, trans_i->u.class)) \
|
||||
|| ((trans_i->assertions & ASSERT_CHAR_CLASS) \
|
||||
&& (tnfa->cflags & REG_ICASE) \
|
||||
&& !tre_isctype(tre_tolower((tre_cint_t)prev_c),trans_i->u.class) \
|
||||
&& !tre_isctype(tre_toupper((tre_cint_t)prev_c),trans_i->u.class)) \
|
||||
|| ((trans_i->assertions & ASSERT_CHAR_CLASS_NEG) \
|
||||
&& tre_neg_char_classes_match(trans_i->neg_classes,(tre_cint_t)prev_c,\
|
||||
tnfa->cflags & REG_ICASE)))
|
||||
|
||||
|
||||
|
||||
|
||||
/* Returns 1 if `t1' wins `t2', 0 otherwise. */
|
||||
inline static int
|
||||
tre_tag_order(int num_tags, tre_tag_direction_t *tag_directions,
|
||||
int *t1, int *t2)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < num_tags; i++)
|
||||
{
|
||||
if (tag_directions[i] == TRE_TAG_MINIMIZE)
|
||||
{
|
||||
if (t1[i] < t2[i])
|
||||
return 1;
|
||||
if (t1[i] > t2[i])
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (t1[i] > t2[i])
|
||||
return 1;
|
||||
if (t1[i] < t2[i])
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
/* assert(0);*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline static int
|
||||
tre_neg_char_classes_match(tre_ctype_t *classes, tre_cint_t wc, int icase)
|
||||
{
|
||||
DPRINT(("neg_char_classes_test: %p, %d, %d\n", classes, wc, icase));
|
||||
while (*classes != (tre_ctype_t)0)
|
||||
if ((!icase && tre_isctype(wc, *classes))
|
||||
|| (icase && (tre_isctype(tre_toupper(wc), *classes)
|
||||
|| tre_isctype(tre_tolower(wc), *classes))))
|
||||
return 1; /* Match. */
|
||||
else
|
||||
classes++;
|
||||
return 0; /* No match. */
|
||||
}
|
||||
155
deps/tre/lib/tre-mem.c
vendored
Normal file
155
deps/tre/lib/tre-mem.c
vendored
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
tre-mem.c - TRE memory allocator
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
This memory allocator is for allocating small memory blocks efficiently
|
||||
in terms of memory overhead and execution speed. The allocated blocks
|
||||
cannot be freed individually, only all at once. There can be multiple
|
||||
allocators, though.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "tre-internal.h"
|
||||
#include "tre-mem.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
|
||||
/* Returns a new memory allocator or NULL if out of memory. */
|
||||
tre_mem_t
|
||||
tre_mem_new_impl(int provided, void *provided_block)
|
||||
{
|
||||
tre_mem_t mem;
|
||||
if (provided)
|
||||
{
|
||||
mem = provided_block;
|
||||
memset(mem, 0, sizeof(*mem));
|
||||
}
|
||||
else
|
||||
mem = xcalloc(1, sizeof(*mem));
|
||||
if (mem == NULL)
|
||||
return NULL;
|
||||
return mem;
|
||||
}
|
||||
|
||||
|
||||
/* Frees the memory allocator and all memory allocated with it. */
|
||||
void
|
||||
tre_mem_destroy(tre_mem_t mem)
|
||||
{
|
||||
tre_list_t *tmp, *l = mem->blocks;
|
||||
|
||||
while (l != NULL)
|
||||
{
|
||||
xfree(l->data);
|
||||
tmp = l->next;
|
||||
xfree(l);
|
||||
l = tmp;
|
||||
}
|
||||
xfree(mem);
|
||||
}
|
||||
|
||||
|
||||
/* Allocates a block of `size' bytes from `mem'. Returns a pointer to the
|
||||
allocated block or NULL if an underlying malloc() failed. */
|
||||
void *
|
||||
tre_mem_alloc_impl(tre_mem_t mem, int provided, void *provided_block,
|
||||
int zero, size_t size)
|
||||
{
|
||||
void *ptr;
|
||||
|
||||
if (mem->failed)
|
||||
{
|
||||
DPRINT(("tre_mem_alloc: oops, called after failure?!\n"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef MALLOC_DEBUGGING
|
||||
if (!provided)
|
||||
{
|
||||
ptr = xmalloc(1);
|
||||
if (ptr == NULL)
|
||||
{
|
||||
DPRINT(("tre_mem_alloc: xmalloc forced failure\n"));
|
||||
mem->failed = 1;
|
||||
return NULL;
|
||||
}
|
||||
xfree(ptr);
|
||||
}
|
||||
#endif /* MALLOC_DEBUGGING */
|
||||
|
||||
if (mem->n < size)
|
||||
{
|
||||
/* We need more memory than is available in the current block.
|
||||
Allocate a new block. */
|
||||
tre_list_t *l;
|
||||
if (provided)
|
||||
{
|
||||
DPRINT(("tre_mem_alloc: using provided block\n"));
|
||||
if (provided_block == NULL)
|
||||
{
|
||||
DPRINT(("tre_mem_alloc: provided block was NULL\n"));
|
||||
mem->failed = 1;
|
||||
return NULL;
|
||||
}
|
||||
mem->ptr = provided_block;
|
||||
mem->n = TRE_MEM_BLOCK_SIZE;
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t block_size;
|
||||
if (size * 8 > TRE_MEM_BLOCK_SIZE)
|
||||
block_size = size * 8;
|
||||
else
|
||||
block_size = TRE_MEM_BLOCK_SIZE;
|
||||
DPRINT(("tre_mem_alloc: allocating new %zu byte block\n",
|
||||
block_size));
|
||||
l = xmalloc(sizeof(*l));
|
||||
if (l == NULL)
|
||||
{
|
||||
mem->failed = 1;
|
||||
return NULL;
|
||||
}
|
||||
l->data = xmalloc(block_size);
|
||||
if (l->data == NULL)
|
||||
{
|
||||
xfree(l);
|
||||
mem->failed = 1;
|
||||
return NULL;
|
||||
}
|
||||
l->next = NULL;
|
||||
if (mem->current != NULL)
|
||||
mem->current->next = l;
|
||||
if (mem->blocks == NULL)
|
||||
mem->blocks = l;
|
||||
mem->current = l;
|
||||
mem->ptr = l->data;
|
||||
mem->n = block_size;
|
||||
}
|
||||
}
|
||||
|
||||
/* Make sure the next pointer will be aligned. */
|
||||
size += ALIGN(mem->ptr + size, long);
|
||||
|
||||
/* Allocate from current block. */
|
||||
ptr = mem->ptr;
|
||||
mem->ptr += size;
|
||||
mem->n -= size;
|
||||
|
||||
/* Set to zero if needed. */
|
||||
if (zero)
|
||||
memset(ptr, 0, size);
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/* EOF */
|
||||
66
deps/tre/lib/tre-mem.h
vendored
Normal file
66
deps/tre/lib/tre-mem.h
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
tre-mem.h - TRE memory allocator interface
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef TRE_MEM_H
|
||||
#define TRE_MEM_H 1
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#define TRE_MEM_BLOCK_SIZE 1024
|
||||
|
||||
typedef struct tre_list {
|
||||
void *data;
|
||||
struct tre_list *next;
|
||||
} tre_list_t;
|
||||
|
||||
typedef struct tre_mem_struct {
|
||||
tre_list_t *blocks;
|
||||
tre_list_t *current;
|
||||
char *ptr;
|
||||
size_t n;
|
||||
int failed;
|
||||
void **provided;
|
||||
} *tre_mem_t;
|
||||
|
||||
|
||||
tre_mem_t tre_mem_new_impl(int provided, void *provided_block);
|
||||
void *tre_mem_alloc_impl(tre_mem_t mem, int provided, void *provided_block,
|
||||
int zero, size_t size);
|
||||
|
||||
/* Returns a new memory allocator or NULL if out of memory. */
|
||||
#define tre_mem_new() tre_mem_new_impl(0, NULL)
|
||||
|
||||
/* Allocates a block of `size' bytes from `mem'. Returns a pointer to the
|
||||
allocated block or NULL if an underlying malloc() failed. */
|
||||
#define tre_mem_alloc(mem, size) tre_mem_alloc_impl(mem, 0, NULL, 0, size)
|
||||
|
||||
/* Allocates a block of `size' bytes from `mem'. Returns a pointer to the
|
||||
allocated block or NULL if an underlying malloc() failed. The memory
|
||||
is set to zero. */
|
||||
#define tre_mem_calloc(mem, size) tre_mem_alloc_impl(mem, 0, NULL, 1, size)
|
||||
|
||||
#ifdef TRE_USE_ALLOCA
|
||||
/* alloca() versions. Like above, but memory is allocated with alloca()
|
||||
instead of malloc(). */
|
||||
|
||||
#define tre_mem_newa() \
|
||||
tre_mem_new_impl(1, alloca(sizeof(struct tre_mem_struct)))
|
||||
|
||||
#define tre_mem_alloca(mem, size) \
|
||||
((mem)->n >= (size) \
|
||||
? tre_mem_alloc_impl((mem), 1, NULL, 0, (size)) \
|
||||
: tre_mem_alloc_impl((mem), 1, alloca(TRE_MEM_BLOCK_SIZE), 0, (size)))
|
||||
#endif /* TRE_USE_ALLOCA */
|
||||
|
||||
|
||||
/* Frees the memory allocator and all memory allocated with it. */
|
||||
void tre_mem_destroy(tre_mem_t mem);
|
||||
|
||||
#endif /* TRE_MEM_H */
|
||||
|
||||
/* EOF */
|
||||
1758
deps/tre/lib/tre-parse.c
vendored
Normal file
1758
deps/tre/lib/tre-parse.c
vendored
Normal file
File diff suppressed because it is too large
Load diff
52
deps/tre/lib/tre-parse.h
vendored
Normal file
52
deps/tre/lib/tre-parse.h
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
tre-parse.c - Regexp parser definitions
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef TRE_PARSE_H
|
||||
#define TRE_PARSE_H 1
|
||||
|
||||
/* Parse context. */
|
||||
typedef struct {
|
||||
/* Memory allocator. The AST is allocated using this. */
|
||||
tre_mem_t mem;
|
||||
/* Stack used for keeping track of regexp syntax. */
|
||||
tre_stack_t *stack;
|
||||
/* The parse result. */
|
||||
tre_ast_node_t *result;
|
||||
/* The regexp to parse and its length. */
|
||||
const tre_char_t *re;
|
||||
/* The first character of the entire regexp. */
|
||||
const tre_char_t *re_start;
|
||||
/* The first character after the end of the regexp. */
|
||||
const tre_char_t *re_end;
|
||||
size_t len;
|
||||
/* Current submatch ID. */
|
||||
int submatch_id;
|
||||
/* The highest back reference or -1 if none seen so far. */
|
||||
int max_backref;
|
||||
/* This flag is set if the regexp uses approximate matching. */
|
||||
int have_approx;
|
||||
/* This flag is set if the regexp changes cflags inline using (?...) */
|
||||
int have_inline_cflags;
|
||||
/* Compilation flags. */
|
||||
int cflags;
|
||||
/* If this flag is set the top-level submatch is not captured. */
|
||||
int nofirstsub;
|
||||
/* The currently set approximate matching parameters. */
|
||||
int params[TRE_PARAM_LAST];
|
||||
/* the MB_CUR_MAX in use */
|
||||
int mb_cur_max;
|
||||
} tre_parse_ctx_t;
|
||||
|
||||
/* Parses a wide character regexp pattern into a syntax tree. This parser
|
||||
handles both syntaxes (BRE and ERE), including the TRE extensions. */
|
||||
reg_errcode_t
|
||||
tre_parse(tre_parse_ctx_t *ctx);
|
||||
|
||||
#endif /* TRE_PARSE_H */
|
||||
|
||||
/* EOF */
|
||||
123
deps/tre/lib/tre-stack.c
vendored
Normal file
123
deps/tre/lib/tre-stack.c
vendored
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
tre-stack.c - Simple stack implementation
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "tre-internal.h"
|
||||
#include "tre-stack.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
union tre_stack_item {
|
||||
void *voidptr_value;
|
||||
int int_value;
|
||||
};
|
||||
|
||||
struct tre_stack_rec {
|
||||
size_t size;
|
||||
size_t max_size;
|
||||
size_t ptr;
|
||||
union tre_stack_item *stack;
|
||||
};
|
||||
|
||||
|
||||
tre_stack_t *
|
||||
tre_stack_new(size_t size, size_t max_size)
|
||||
{
|
||||
tre_stack_t *s;
|
||||
|
||||
s = xmalloc(sizeof(*s));
|
||||
if (s != NULL)
|
||||
{
|
||||
s->stack = xmalloc(sizeof(*s->stack) * size);
|
||||
if (s->stack == NULL)
|
||||
{
|
||||
xfree(s);
|
||||
return NULL;
|
||||
}
|
||||
s->size = size;
|
||||
s->max_size = max_size;
|
||||
s->ptr = 0;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void
|
||||
tre_stack_destroy(tre_stack_t *s)
|
||||
{
|
||||
xfree(s->stack);
|
||||
xfree(s);
|
||||
}
|
||||
|
||||
size_t
|
||||
tre_stack_num_items(tre_stack_t *s)
|
||||
{
|
||||
return s->ptr;
|
||||
}
|
||||
|
||||
static reg_errcode_t
|
||||
tre_stack_push(tre_stack_t *s, union tre_stack_item value)
|
||||
{
|
||||
if (s->ptr < s->size)
|
||||
{
|
||||
s->stack[s->ptr] = value;
|
||||
s->ptr++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (s->size >= s->max_size)
|
||||
{
|
||||
DPRINT(("tre_stack_push: stack full\n"));
|
||||
return REG_ESPACE;
|
||||
}
|
||||
else
|
||||
{
|
||||
union tre_stack_item *new_buffer;
|
||||
size_t new_size;
|
||||
DPRINT(("tre_stack_push: trying to realloc more space\n"));
|
||||
new_size = s->size + s->size;
|
||||
if (new_size > s->max_size)
|
||||
new_size = s->max_size;
|
||||
new_buffer = xrealloc(s->stack, sizeof(*new_buffer) * new_size);
|
||||
if (new_buffer == NULL)
|
||||
{
|
||||
DPRINT(("tre_stack_push: realloc failed.\n"));
|
||||
return REG_ESPACE;
|
||||
}
|
||||
DPRINT(("tre_stack_push: realloc succeeded.\n"));
|
||||
assert(new_size > s->size);
|
||||
s->size = new_size;
|
||||
s->stack = new_buffer;
|
||||
tre_stack_push(s, value);
|
||||
}
|
||||
}
|
||||
return REG_OK;
|
||||
}
|
||||
|
||||
#define define_pushf(typetag, type) \
|
||||
declare_pushf(typetag, type) { \
|
||||
union tre_stack_item item; \
|
||||
item.typetag ## _value = value; \
|
||||
return tre_stack_push(s, item); \
|
||||
}
|
||||
|
||||
define_pushf(int, int)
|
||||
define_pushf(voidptr, void *)
|
||||
|
||||
#define define_popf(typetag, type) \
|
||||
declare_popf(typetag, type) { \
|
||||
return s->stack[--s->ptr].typetag ## _value; \
|
||||
}
|
||||
|
||||
define_popf(int, int)
|
||||
define_popf(voidptr, void *)
|
||||
|
||||
/* EOF */
|
||||
76
deps/tre/lib/tre-stack.h
vendored
Normal file
76
deps/tre/lib/tre-stack.h
vendored
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
tre-stack.h: Stack definitions
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#ifndef TRE_STACK_H
|
||||
#define TRE_STACK_H 1
|
||||
|
||||
#include "../local_includes/tre.h"
|
||||
|
||||
typedef struct tre_stack_rec tre_stack_t;
|
||||
|
||||
/* Creates a new stack object with initial size `size' and maximum size
|
||||
`max_size'. Pushing an additional item onto a full stack will resize
|
||||
the stack to double its capacity until the maximum is reached. Returns
|
||||
the stack object or NULL if out of memory. */
|
||||
tre_stack_t *
|
||||
tre_stack_new(size_t size, size_t max_size);
|
||||
|
||||
/* Frees the stack object. */
|
||||
void
|
||||
tre_stack_destroy(tre_stack_t *s);
|
||||
|
||||
/* Returns the current number of items on the stack. */
|
||||
size_t
|
||||
tre_stack_num_items(tre_stack_t *s);
|
||||
|
||||
/* Each tre_stack_push_*(tre_stack_t *s, <type> value) function pushes
|
||||
`value' on top of stack `s'. Returns REG_ESPACE if out of memory.
|
||||
This tries to realloc() more space before failing if maximum size
|
||||
has not yet been reached. Returns REG_OK if successful. */
|
||||
#define declare_pushf(typetag, type) \
|
||||
reg_errcode_t tre_stack_push_ ## typetag(tre_stack_t *s, type value)
|
||||
|
||||
declare_pushf(voidptr, void *);
|
||||
declare_pushf(int, int);
|
||||
|
||||
/* Each tre_stack_pop_*(tre_stack_t *s) function pops the topmost
|
||||
element off of stack `s' and returns it. The stack must not be
|
||||
empty. */
|
||||
#define declare_popf(typetag, type) \
|
||||
type tre_stack_pop_ ## typetag(tre_stack_t *s)
|
||||
|
||||
declare_popf(voidptr, void *);
|
||||
declare_popf(int, int);
|
||||
|
||||
/* Just to save some typing. */
|
||||
#define STACK_PUSH(s, typetag, value) \
|
||||
do \
|
||||
{ \
|
||||
status = tre_stack_push_ ## typetag(s, value); \
|
||||
} \
|
||||
while (/*CONSTCOND*/(void)0,0)
|
||||
|
||||
#define STACK_PUSHX(s, typetag, value) \
|
||||
{ \
|
||||
status = tre_stack_push_ ## typetag(s, value); \
|
||||
if (status != REG_OK) \
|
||||
break; \
|
||||
}
|
||||
|
||||
#define STACK_PUSHR(s, typetag, value) \
|
||||
{ \
|
||||
reg_errcode_t _status; \
|
||||
_status = tre_stack_push_ ## typetag(s, value); \
|
||||
if (_status != REG_OK) \
|
||||
return _status; \
|
||||
}
|
||||
|
||||
#endif /* TRE_STACK_H */
|
||||
|
||||
/* EOF */
|
||||
362
deps/tre/lib/xmalloc.c
vendored
Normal file
362
deps/tre/lib/xmalloc.c
vendored
Normal file
|
|
@ -0,0 +1,362 @@
|
|||
/*
|
||||
xmalloc.c - Simple malloc debugging library implementation
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
TODO:
|
||||
- red zones
|
||||
- group dumps by source location
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#define XMALLOC_INTERNAL 1
|
||||
#include "xmalloc.h"
|
||||
|
||||
|
||||
/*
|
||||
Internal stuff.
|
||||
*/
|
||||
|
||||
typedef struct hashTableItemRec {
|
||||
void *ptr;
|
||||
size_t bytes;
|
||||
const char *file;
|
||||
int line;
|
||||
const char *func;
|
||||
struct hashTableItemRec *next;
|
||||
} hashTableItem;
|
||||
|
||||
typedef struct {
|
||||
hashTableItem **table;
|
||||
} hashTable;
|
||||
|
||||
static int xmalloc_peak;
|
||||
int xmalloc_current;
|
||||
static int xmalloc_peak_blocks;
|
||||
int xmalloc_current_blocks;
|
||||
static int xmalloc_fail_after;
|
||||
|
||||
#define TABLE_BITS 8
|
||||
#define TABLE_MASK ((1 << TABLE_BITS) - 1)
|
||||
#define TABLE_SIZE (1 << TABLE_BITS)
|
||||
|
||||
static hashTable *
|
||||
hash_table_new(void)
|
||||
{
|
||||
hashTable *tbl;
|
||||
|
||||
tbl = malloc(sizeof(*tbl));
|
||||
|
||||
if (tbl != NULL)
|
||||
{
|
||||
tbl->table = calloc(TABLE_SIZE, sizeof(*tbl->table));
|
||||
|
||||
if (tbl->table == NULL)
|
||||
{
|
||||
free(tbl);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return tbl;
|
||||
}
|
||||
|
||||
static unsigned int
|
||||
hash_void_ptr(void *ptr)
|
||||
{
|
||||
unsigned int hash;
|
||||
unsigned int i;
|
||||
|
||||
/* I took this hash function just off the top of my head, I have
|
||||
no idea whether it is bad or very bad. */
|
||||
hash = 0;
|
||||
for (i = 0; i < sizeof(ptr) * 8 / TABLE_BITS; i++)
|
||||
{
|
||||
hash ^= (uintptr_t)ptr >> i * 8;
|
||||
hash += i * 17;
|
||||
hash &= TABLE_MASK;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
static void
|
||||
hash_table_add(hashTable *tbl, void *ptr, size_t bytes,
|
||||
const char *file, int line, const char *func)
|
||||
{
|
||||
unsigned int i;
|
||||
hashTableItem *item, *new;
|
||||
|
||||
i = hash_void_ptr(ptr);
|
||||
|
||||
item = tbl->table[i];
|
||||
if (item != NULL)
|
||||
while (item->next != NULL)
|
||||
item = item->next;
|
||||
|
||||
new = malloc(sizeof(*new));
|
||||
assert(new != NULL);
|
||||
new->ptr = ptr;
|
||||
new->bytes = bytes;
|
||||
new->file = file;
|
||||
new->line = line;
|
||||
new->func = func;
|
||||
new->next = NULL;
|
||||
if (item != NULL)
|
||||
item->next = new;
|
||||
else
|
||||
tbl->table[i] = new;
|
||||
|
||||
xmalloc_current += bytes;
|
||||
if (xmalloc_current > xmalloc_peak)
|
||||
xmalloc_peak = xmalloc_current;
|
||||
xmalloc_current_blocks++;
|
||||
if (xmalloc_current_blocks > xmalloc_peak_blocks)
|
||||
xmalloc_peak_blocks = xmalloc_current_blocks;
|
||||
}
|
||||
|
||||
static void
|
||||
#if defined(__GNUC__) && __GNUC__ >= 10
|
||||
__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__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wuse-after-free"
|
||||
#endif
|
||||
hash_table_del(xmalloc_table, ptr);
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
hash_table_add(xmalloc_table, new_ptr, (int)new_size, file, line, func);
|
||||
}
|
||||
return new_ptr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* EOF */
|
||||
77
deps/tre/lib/xmalloc.h
vendored
Normal file
77
deps/tre/lib/xmalloc.h
vendored
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
xmalloc.h - Simple malloc debugging library API
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef _XMALLOC_H
|
||||
#define _XMALLOC_H 1
|
||||
|
||||
void *xmalloc_impl(size_t size, const char *file, int line, const char *func);
|
||||
void *xcalloc_impl(size_t nmemb, size_t size, const char *file, int line,
|
||||
const char *func);
|
||||
void xfree_impl(void *ptr, const char *file, int line, const char *func);
|
||||
void *xrealloc_impl(void *ptr, size_t new_size, const char *file, int line,
|
||||
const char *func);
|
||||
int xmalloc_dump_leaks(void);
|
||||
void xmalloc_configure(int fail_after);
|
||||
|
||||
|
||||
#ifndef XMALLOC_INTERNAL
|
||||
#ifdef MALLOC_DEBUGGING
|
||||
|
||||
/* Version 2.4 and later of GCC define a magical variable `__PRETTY_FUNCTION__'
|
||||
which contains the name of the function currently being defined.
|
||||
# define __XMALLOC_FUNCTION __PRETTY_FUNCTION__
|
||||
This is broken in G++ before version 2.6.
|
||||
C9x has a similar variable called __func__, but prefer the GCC one since
|
||||
it demangles C++ function names. */
|
||||
# ifdef __GNUC__
|
||||
# if __GNUC__ > 2 || (__GNUC__ == 2 \
|
||||
&& __GNUC_MINOR__ >= (defined __cplusplus ? 6 : 4))
|
||||
# define __XMALLOC_FUNCTION __PRETTY_FUNCTION__
|
||||
# else
|
||||
# define __XMALLOC_FUNCTION ((const char *) 0)
|
||||
# endif
|
||||
# else
|
||||
# if defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L
|
||||
# define __XMALLOC_FUNCTION __func__
|
||||
# else
|
||||
# define __XMALLOC_FUNCTION ((const char *) 0)
|
||||
# endif
|
||||
# endif
|
||||
|
||||
#define xmalloc(size) xmalloc_impl(size, __FILE__, __LINE__, \
|
||||
__XMALLOC_FUNCTION)
|
||||
#define xcalloc(nmemb, size) xcalloc_impl(nmemb, size, __FILE__, __LINE__, \
|
||||
__XMALLOC_FUNCTION)
|
||||
#define xfree(ptr) xfree_impl(ptr, __FILE__, __LINE__, __XMALLOC_FUNCTION)
|
||||
#define xrealloc(ptr, new_size) xrealloc_impl(ptr, new_size, __FILE__, \
|
||||
__LINE__, __XMALLOC_FUNCTION)
|
||||
#undef malloc
|
||||
#undef calloc
|
||||
#undef free
|
||||
#undef realloc
|
||||
|
||||
#define malloc USE_XMALLOC_INSTEAD_OF_MALLOC
|
||||
#define calloc USE_XCALLOC_INSTEAD_OF_CALLOC
|
||||
#define free USE_XFREE_INSTEAD_OF_FREE
|
||||
#define realloc USE_XREALLOC_INSTEAD_OF_REALLOC
|
||||
|
||||
#else /* !MALLOC_DEBUGGING */
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#define xmalloc(size) malloc(size)
|
||||
#define xcalloc(nmemb, size) calloc(nmemb, size)
|
||||
#define xfree(ptr) free(ptr)
|
||||
#define xrealloc(ptr, new_size) realloc(ptr, new_size)
|
||||
|
||||
#endif /* !MALLOC_DEBUGGING */
|
||||
#endif /* !XMALLOC_INTERNAL */
|
||||
|
||||
#endif /* _XMALLOC_H */
|
||||
|
||||
/* EOF */
|
||||
48
deps/tre/local_includes/regex.h
vendored
Normal file
48
deps/tre/local_includes/regex.h
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
regex.h - TRE legacy API
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
This header is for source level compatibility with old code using
|
||||
the <tre/regex.h> header which defined the TRE API functions without
|
||||
a prefix. New code should include <tre/tre.h> instead.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef TRE_REXEX_H
|
||||
#define TRE_REGEX_H 1
|
||||
|
||||
#ifdef USE_LOCAL_TRE_H
|
||||
/* Use the header(s) from the TRE package that this file is part of.
|
||||
(Yes, this file is in local_include too, but the explict path
|
||||
means there is no way to get a system tre.h by accident.) */
|
||||
#include "../local_includes/tre.h"
|
||||
#else
|
||||
/* Use the header(s) from an installed version of the TRE package
|
||||
(so that this application matches the installed libtre),
|
||||
not the one(s) in the local_includes directory. */
|
||||
#include <tre/tre.h>
|
||||
#endif
|
||||
|
||||
#ifndef TRE_USE_SYSTEM_REGEX_H
|
||||
#define regcomp tre_regcomp
|
||||
#define regerror tre_regerror
|
||||
#define regexec tre_regexec
|
||||
#define regfree tre_regfree
|
||||
#endif /* TRE_USE_SYSTEM_REGEX_H */
|
||||
|
||||
#define regacomp tre_regacomp
|
||||
#define regaexec tre_regaexec
|
||||
#define regancomp tre_regancomp
|
||||
#define reganexec tre_reganexec
|
||||
#define regawncomp tre_regawncomp
|
||||
#define regawnexec tre_regawnexec
|
||||
#define regncomp tre_regncomp
|
||||
#define regnexec tre_regnexec
|
||||
#define regwcomp tre_regwcomp
|
||||
#define regwexec tre_regwexec
|
||||
#define regwncomp tre_regwncomp
|
||||
#define regwnexec tre_regwnexec
|
||||
|
||||
#endif /* TRE_REGEX_H */
|
||||
14
deps/tre/local_includes/tre-config.h
vendored
Normal file
14
deps/tre/local_includes/tre-config.h
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/* Minimal TRE configuration for Redis.
|
||||
*
|
||||
* We use TRE as a byte-oriented regex matcher for ARGREP. Redis SDS values are
|
||||
* binary-safe byte strings, so we intentionally keep the dependency build
|
||||
* simple: no wide-char path, no multibyte locale handling, and no approximate
|
||||
* matching engine.
|
||||
*/
|
||||
|
||||
#define HAVE_SYS_TYPES_H 1
|
||||
|
||||
#define TRE_VERSION "redis-vendored"
|
||||
#define TRE_VERSION_1 0
|
||||
#define TRE_VERSION_2 0
|
||||
#define TRE_VERSION_3 0
|
||||
344
deps/tre/local_includes/tre.h
vendored
Normal file
344
deps/tre/local_includes/tre.h
vendored
Normal file
|
|
@ -0,0 +1,344 @@
|
|||
/*
|
||||
tre.h - TRE public API definitions
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef TRE_H
|
||||
#define TRE_H 1
|
||||
|
||||
#ifdef USE_LOCAL_TRE_H
|
||||
/* Make certain to use the header(s) from the TRE package that this
|
||||
file is part of by giving the full path to the header from this directory. */
|
||||
#include "../local_includes/tre-config.h"
|
||||
#else
|
||||
/* Use the header in the same directory as this file if there is one. */
|
||||
#include "tre-config.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SYS_TYPES_H
|
||||
#include <sys/types.h>
|
||||
#endif /* HAVE_SYS_TYPES_H */
|
||||
|
||||
#ifdef HAVE_LIBUTF8_H
|
||||
#include <libutf8.h>
|
||||
#endif /* HAVE_LIBUTF8_H */
|
||||
|
||||
#ifdef TRE_USE_SYSTEM_REGEX_H
|
||||
/* Include the system regex.h to make TRE ABI compatible with the
|
||||
system regex. */
|
||||
#include TRE_SYSTEM_REGEX_H_PATH
|
||||
#define tre_regcomp regcomp
|
||||
#define tre_regexec regexec
|
||||
#define tre_regerror regerror
|
||||
#define tre_regfree regfree
|
||||
/* The GNU C regex has a number of refinements to the POSIX standard for the
|
||||
formal parameter list of the regexec() function, and some of those fail to
|
||||
compile when using LLVM. The refinements seem to be opt-out rather than
|
||||
opt-in when using a recent gcc, and they produce a warning when TRE tries
|
||||
to mimic the API without the refinements. The TRE code still works but
|
||||
the warnings are distracting, so try to #define a flag to indicate when to
|
||||
add the refinements to TRE's parameter list too. */
|
||||
#ifdef __GNUC__
|
||||
/* Try to test something that looks pretty REGEX specific and hope we don't
|
||||
need a zillion different platform+compiler specific tests to deal with this. */
|
||||
#ifdef _REGEX_NELTS
|
||||
/* Define a TRE specific flag here so that:
|
||||
1) there is only one place where code has to be changed if the test above is not adequate, and
|
||||
2) the flag can be used in any other parts of the TRE source that might be affected by the
|
||||
GNUC refinements.
|
||||
Note that this flag is only defined when all of TRE_USE_SYSTEM_REGEX_H, __GNUC__, and _REGEX_NELTS are defined. */
|
||||
#define TRE_USE_GNUC_REGEXEC_FPL 1
|
||||
#endif
|
||||
#endif
|
||||
#endif /* TRE_USE_SYSTEM_REGEX_H */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef TRE_USE_SYSTEM_REGEX_H
|
||||
|
||||
#ifndef REG_OK
|
||||
#define REG_OK 0
|
||||
#endif /* !REG_OK */
|
||||
|
||||
#ifndef HAVE_REG_ERRCODE_T
|
||||
typedef int reg_errcode_t;
|
||||
#endif /* !HAVE_REG_ERRCODE_T */
|
||||
|
||||
#if !defined(REG_NOSPEC) && !defined(REG_LITERAL)
|
||||
#define REG_LITERAL 0x1000
|
||||
#endif
|
||||
|
||||
/* Extra tre_regcomp() return error codes. */
|
||||
#define REG_BADMAX REG_BADBR
|
||||
|
||||
/* Extra tre_regcomp() flags. */
|
||||
#ifndef REG_BASIC
|
||||
#define REG_BASIC 0
|
||||
#endif /* !REG_BASIC */
|
||||
#define REG_RIGHT_ASSOC (REG_LITERAL << 1)
|
||||
#ifdef REG_UNGREEDY
|
||||
/* We're going to use TRE code, so we need the TRE define (dodge problem in MacOS). */
|
||||
#undef REG_UNGREEDY
|
||||
#endif
|
||||
#define REG_UNGREEDY (REG_RIGHT_ASSOC << 1)
|
||||
|
||||
#define REG_USEBYTES (REG_UNGREEDY << 1)
|
||||
|
||||
/* Extra tre_regexec() flags. */
|
||||
#define REG_APPROX_MATCHER 0x1000
|
||||
#ifdef REG_BACKTRACKING_MATCHER
|
||||
/* We're going to use TRE code, so we need the TRE define (dodge problem in MacOS). */
|
||||
#undef REG_BACKTRACKING_MATCHER
|
||||
#endif
|
||||
#define REG_BACKTRACKING_MATCHER (REG_APPROX_MATCHER << 1)
|
||||
|
||||
#else /* !TRE_USE_SYSTEM_REGEX_H */
|
||||
|
||||
/* If the we're not using system regex.h, we need to define the
|
||||
structs and enums ourselves. */
|
||||
|
||||
typedef int regoff_t;
|
||||
typedef struct {
|
||||
size_t re_nsub; /* Number of parenthesized subexpressions. */
|
||||
void *value; /* For internal use only. */
|
||||
} regex_t;
|
||||
|
||||
typedef struct {
|
||||
regoff_t rm_so;
|
||||
regoff_t rm_eo;
|
||||
} regmatch_t;
|
||||
|
||||
|
||||
typedef enum {
|
||||
REG_OK = 0, /* No error. */
|
||||
/* POSIX tre_regcomp() return error codes. (In the order listed in the
|
||||
standard.) */
|
||||
REG_NOMATCH, /* No match. */
|
||||
REG_BADPAT, /* Invalid regexp. */
|
||||
REG_ECOLLATE, /* Unknown collating element. */
|
||||
REG_ECTYPE, /* Unknown character class name. */
|
||||
REG_EESCAPE, /* Trailing backslash. */
|
||||
REG_ESUBREG, /* Invalid back reference. */
|
||||
REG_EBRACK, /* "[]" imbalance */
|
||||
REG_EPAREN, /* "\(\)" or "()" imbalance */
|
||||
REG_EBRACE, /* "\{\}" or "{}" imbalance */
|
||||
REG_BADBR, /* Invalid content of {} */
|
||||
REG_ERANGE, /* Invalid use of range operator */
|
||||
REG_ESPACE, /* Out of memory. */
|
||||
REG_BADRPT, /* Invalid use of repetition operators. */
|
||||
REG_BADMAX, /* Maximum repetition in {} too large */
|
||||
} reg_errcode_t;
|
||||
|
||||
/* POSIX tre_regcomp() flags. */
|
||||
#define REG_EXTENDED 1
|
||||
#define REG_ICASE (REG_EXTENDED << 1)
|
||||
#define REG_NEWLINE (REG_ICASE << 1)
|
||||
#define REG_NOSUB (REG_NEWLINE << 1)
|
||||
|
||||
/* Extra tre_regcomp() flags. */
|
||||
#define REG_BASIC 0
|
||||
#define REG_LITERAL (REG_NOSUB << 1)
|
||||
#define REG_RIGHT_ASSOC (REG_LITERAL << 1)
|
||||
#define REG_UNGREEDY (REG_RIGHT_ASSOC << 1)
|
||||
#define REG_USEBYTES (REG_UNGREEDY << 1)
|
||||
|
||||
/* POSIX tre_regexec() flags. */
|
||||
#define REG_NOTBOL 1
|
||||
#define REG_NOTEOL (REG_NOTBOL << 1)
|
||||
|
||||
/* Extra tre_regexec() flags. */
|
||||
#define REG_APPROX_MATCHER (REG_NOTEOL << 1)
|
||||
#define REG_BACKTRACKING_MATCHER (REG_APPROX_MATCHER << 1)
|
||||
|
||||
#endif /* !TRE_USE_SYSTEM_REGEX_H */
|
||||
|
||||
/* REG_NOSPEC and REG_LITERAL mean the same thing. */
|
||||
#if defined(REG_LITERAL) && !defined(REG_NOSPEC)
|
||||
#define REG_NOSPEC REG_LITERAL
|
||||
#elif defined(REG_NOSPEC) && !defined(REG_LITERAL)
|
||||
#define REG_LITERAL REG_NOSPEC
|
||||
#endif /* defined(REG_NOSPEC) */
|
||||
|
||||
/* The maximum number of iterations in a bound expression. */
|
||||
#undef RE_DUP_MAX
|
||||
#define RE_DUP_MAX 255
|
||||
|
||||
/* The POSIX.2 regexp functions */
|
||||
extern int
|
||||
tre_regcomp(regex_t *preg, const char *regex, int cflags);
|
||||
|
||||
#ifdef TRE_USE_GNUC_REGEXEC_FPL
|
||||
extern int
|
||||
tre_regexec(const regex_t *preg, const char *string,
|
||||
size_t nmatch, regmatch_t pmatch[_Restrict_arr_ _REGEX_NELTS (nmatch)],
|
||||
int eflags);
|
||||
#else
|
||||
extern int
|
||||
tre_regexec(const regex_t *preg, const char *string, size_t nmatch,
|
||||
regmatch_t pmatch[], int eflags);
|
||||
#endif
|
||||
|
||||
extern int
|
||||
tre_regcompb(regex_t *preg, const char *regex, int cflags);
|
||||
|
||||
extern int
|
||||
tre_regexecb(const regex_t *preg, const char *string, size_t nmatch,
|
||||
regmatch_t pmatch[], int eflags);
|
||||
|
||||
extern size_t
|
||||
tre_regerror(int errcode, const regex_t *preg, char *errbuf,
|
||||
size_t errbuf_size);
|
||||
|
||||
extern void
|
||||
tre_regfree(regex_t *preg);
|
||||
|
||||
#ifdef TRE_WCHAR
|
||||
#ifdef HAVE_WCHAR_H
|
||||
#include <wchar.h>
|
||||
#endif /* HAVE_WCHAR_H */
|
||||
|
||||
/* Wide character versions (not in POSIX.2). */
|
||||
extern int
|
||||
tre_regwcomp(regex_t *preg, const wchar_t *regex, int cflags);
|
||||
|
||||
extern int
|
||||
tre_regwexec(const regex_t *preg, const wchar_t *string,
|
||||
size_t nmatch, regmatch_t pmatch[], int eflags);
|
||||
#endif /* TRE_WCHAR */
|
||||
|
||||
/* Versions with a maximum length argument and therefore the capability to
|
||||
handle null characters in the middle of the strings (not in POSIX.2). */
|
||||
extern int
|
||||
tre_regncomp(regex_t *preg, const char *regex, size_t len, int cflags);
|
||||
|
||||
extern int
|
||||
tre_regnexec(const regex_t *preg, const char *string, size_t len,
|
||||
size_t nmatch, regmatch_t pmatch[], int eflags);
|
||||
|
||||
/* regn*b versions take byte literally as 8-bit values */
|
||||
extern int
|
||||
tre_regncompb(regex_t *preg, const char *regex, size_t n, int cflags);
|
||||
|
||||
extern int
|
||||
tre_regnexecb(const regex_t *preg, const char *str, size_t len,
|
||||
size_t nmatch, regmatch_t pmatch[], int eflags);
|
||||
|
||||
#ifdef TRE_WCHAR
|
||||
extern int
|
||||
tre_regwncomp(regex_t *preg, const wchar_t *regex, size_t len, int cflags);
|
||||
|
||||
extern int
|
||||
tre_regwnexec(const regex_t *preg, const wchar_t *string, size_t len,
|
||||
size_t nmatch, regmatch_t pmatch[], int eflags);
|
||||
#endif /* TRE_WCHAR */
|
||||
|
||||
#ifdef TRE_APPROX
|
||||
|
||||
/* Approximate matching parameter struct. */
|
||||
typedef struct {
|
||||
int cost_ins; /* Default cost of an inserted character. */
|
||||
int cost_del; /* Default cost of a deleted character. */
|
||||
int cost_subst; /* Default cost of a substituted character. */
|
||||
int max_cost; /* Maximum allowed cost of a match. */
|
||||
|
||||
int max_ins; /* Maximum allowed number of inserts. */
|
||||
int max_del; /* Maximum allowed number of deletes. */
|
||||
int max_subst; /* Maximum allowed number of substitutes. */
|
||||
int max_err; /* Maximum allowed number of errors total. */
|
||||
} regaparams_t;
|
||||
|
||||
/* Approximate matching result struct. */
|
||||
typedef struct {
|
||||
size_t nmatch; /* Length of pmatch[] array. */
|
||||
regmatch_t *pmatch; /* Submatch data. */
|
||||
int cost; /* Cost of the match. */
|
||||
int num_ins; /* Number of inserts in the match. */
|
||||
int num_del; /* Number of deletes in the match. */
|
||||
int num_subst; /* Number of substitutes in the match. */
|
||||
} regamatch_t;
|
||||
|
||||
|
||||
/* Approximate matching functions. */
|
||||
extern int
|
||||
tre_regaexec(const regex_t *preg, const char *string,
|
||||
regamatch_t *match, regaparams_t params, int eflags);
|
||||
|
||||
extern int
|
||||
tre_reganexec(const regex_t *preg, const char *string, size_t len,
|
||||
regamatch_t *match, regaparams_t params, int eflags);
|
||||
|
||||
extern int
|
||||
tre_regaexecb(const regex_t *preg, const char *string,
|
||||
regamatch_t *match, regaparams_t params, int eflags);
|
||||
|
||||
#ifdef TRE_WCHAR
|
||||
/* Wide character approximate matching. */
|
||||
extern int
|
||||
tre_regawexec(const regex_t *preg, const wchar_t *string,
|
||||
regamatch_t *match, regaparams_t params, int eflags);
|
||||
|
||||
extern int
|
||||
tre_regawnexec(const regex_t *preg, const wchar_t *string, size_t len,
|
||||
regamatch_t *match, regaparams_t params, int eflags);
|
||||
#endif /* TRE_WCHAR */
|
||||
|
||||
/* Sets the parameters to default values. */
|
||||
extern void
|
||||
tre_regaparams_default(regaparams_t *params);
|
||||
#endif /* TRE_APPROX */
|
||||
|
||||
#ifdef TRE_WCHAR
|
||||
typedef wchar_t tre_char_t;
|
||||
#else /* !TRE_WCHAR */
|
||||
typedef unsigned char tre_char_t;
|
||||
#endif /* !TRE_WCHAR */
|
||||
|
||||
typedef struct {
|
||||
int (*get_next_char)(tre_char_t *c, unsigned int *pos_add, void *context);
|
||||
void (*rewind)(size_t pos, void *context);
|
||||
int (*compare)(size_t pos1, size_t pos2, size_t len, void *context);
|
||||
void *context;
|
||||
} tre_str_source;
|
||||
|
||||
extern int
|
||||
tre_reguexec(const regex_t *preg, const tre_str_source *string,
|
||||
size_t nmatch, regmatch_t pmatch[], int eflags);
|
||||
|
||||
/* Returns the version string. The returned string is static. */
|
||||
extern char *
|
||||
tre_version(void);
|
||||
|
||||
/* Returns the value for a config parameter. The type to which `result'
|
||||
must point to depends of the value of `query', see documentation for
|
||||
more details. */
|
||||
extern int
|
||||
tre_config(int query, void *result);
|
||||
|
||||
enum {
|
||||
TRE_CONFIG_APPROX,
|
||||
TRE_CONFIG_WCHAR,
|
||||
TRE_CONFIG_MULTIBYTE,
|
||||
TRE_CONFIG_SYSTEM_ABI,
|
||||
TRE_CONFIG_VERSION
|
||||
};
|
||||
|
||||
/* Returns 1 if the compiled pattern has back references, 0 if not. */
|
||||
extern int
|
||||
tre_have_backrefs(const regex_t *preg);
|
||||
|
||||
/* Returns 1 if the compiled pattern uses approximate matching features,
|
||||
0 if not. */
|
||||
extern int
|
||||
tre_have_approx(const regex_t *preg);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* TRE_H */
|
||||
|
||||
/* EOF */
|
||||
1871
deps/tre/tests/retest.c
vendored
Normal file
1871
deps/tre/tests/retest.c
vendored
Normal file
File diff suppressed because it is too large
Load diff
303
deps/tre/tests/test-literal-opt.c
vendored
Normal file
303
deps/tre/tests/test-literal-opt.c
vendored
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
/*
|
||||
test-literal-opt.c - Validate TRE literal optimization against the
|
||||
generic matcher.
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
|
||||
#include <locale.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "tre-internal.h"
|
||||
|
||||
#define PMATCH_SLOTS 4
|
||||
#define RC_ANY -9999
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
const char *pattern;
|
||||
size_t pattern_len;
|
||||
int cflags;
|
||||
const char *string;
|
||||
size_t string_len;
|
||||
int eflags;
|
||||
int expected_rc;
|
||||
tre_literal_opt_mode_t expected_mode;
|
||||
} litopt_case_t;
|
||||
|
||||
static void
|
||||
init_pmatch(regmatch_t pmatch[], size_t count)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
pmatch[i].rm_so = 111;
|
||||
pmatch[i].rm_eo = 222;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
same_pmatch(const regmatch_t a[], const regmatch_t b[], size_t count)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
if (a[i].rm_so != b[i].rm_so || a[i].rm_eo != b[i].rm_eo)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
pmatch_cleared(const regmatch_t pmatch[], size_t count)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
if (pmatch[i].rm_so != -1 || pmatch[i].rm_eo != -1)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
run_case(const litopt_case_t *tc)
|
||||
{
|
||||
regex_t preg;
|
||||
tre_tnfa_t *tnfa;
|
||||
regmatch_t fast[PMATCH_SLOTS], slow[PMATCH_SLOTS];
|
||||
tre_literal_opt_mode_t saved_mode;
|
||||
char errbuf[256];
|
||||
int errcode, fast_rc, slow_rc;
|
||||
|
||||
memset(&preg, 0, sizeof(preg));
|
||||
errcode = tre_regncompb(&preg, tc->pattern, tc->pattern_len, tc->cflags);
|
||||
if (errcode != REG_OK)
|
||||
{
|
||||
tre_regerror(errcode, &preg, errbuf, sizeof(errbuf));
|
||||
fprintf(stderr, "%s: compile failed: %s\n", tc->name, errbuf);
|
||||
return 1;
|
||||
}
|
||||
|
||||
tnfa = (tre_tnfa_t *)preg.value;
|
||||
if (tnfa->literal_opt.mode != tc->expected_mode)
|
||||
{
|
||||
fprintf(stderr, "%s: optimizer mode %d, expected %d\n",
|
||||
tc->name, (int)tnfa->literal_opt.mode, (int)tc->expected_mode);
|
||||
tre_regfree(&preg);
|
||||
return 1;
|
||||
}
|
||||
|
||||
init_pmatch(fast, PMATCH_SLOTS);
|
||||
init_pmatch(slow, PMATCH_SLOTS);
|
||||
|
||||
fast_rc = tre_regnexecb(&preg, tc->string, tc->string_len,
|
||||
PMATCH_SLOTS, fast, tc->eflags);
|
||||
|
||||
saved_mode = tnfa->literal_opt.mode;
|
||||
tnfa->literal_opt.mode = TRE_LITERAL_OPT_NONE;
|
||||
slow_rc = tre_regnexecb(&preg, tc->string, tc->string_len,
|
||||
PMATCH_SLOTS, slow, tc->eflags);
|
||||
tnfa->literal_opt.mode = saved_mode;
|
||||
|
||||
if (fast_rc != slow_rc)
|
||||
{
|
||||
fprintf(stderr, "%s: fast rc %d, slow rc %d\n",
|
||||
tc->name, fast_rc, slow_rc);
|
||||
tre_regfree(&preg);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (tc->expected_rc != RC_ANY && fast_rc != tc->expected_rc)
|
||||
{
|
||||
fprintf(stderr, "%s: rc %d, expected %d\n",
|
||||
tc->name, fast_rc, tc->expected_rc);
|
||||
tre_regfree(&preg);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!same_pmatch(fast, slow, PMATCH_SLOTS))
|
||||
{
|
||||
fprintf(stderr, "%s: fast and slow pmatch differ\n", tc->name);
|
||||
tre_regfree(&preg);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ((tc->cflags & REG_NOSUB) && fast_rc == REG_OK
|
||||
&& !pmatch_cleared(fast, PMATCH_SLOTS))
|
||||
{
|
||||
fprintf(stderr, "%s: REG_NOSUB match did not clear pmatch\n", tc->name);
|
||||
tre_regfree(&preg);
|
||||
return 1;
|
||||
}
|
||||
|
||||
tre_regfree(&preg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
static const char nonascii_pattern[] = { (char)0xc0, '|', (char)0xe0 };
|
||||
static const char nonascii_haystack[] = { 'x', (char)0xe0, 'y' };
|
||||
static const litopt_case_t cases[] = {
|
||||
{
|
||||
"contains basic",
|
||||
"foo|bar|baz",
|
||||
sizeof("foo|bar|baz") - 1,
|
||||
REG_EXTENDED | REG_NOSUB,
|
||||
"xxbaryy",
|
||||
sizeof("xxbaryy") - 1,
|
||||
0,
|
||||
REG_OK,
|
||||
TRE_LITERAL_OPT_CONTAINS
|
||||
},
|
||||
{
|
||||
"contains ignores bol/eol flags",
|
||||
"foo|bar|baz",
|
||||
sizeof("foo|bar|baz") - 1,
|
||||
REG_EXTENDED | REG_NOSUB,
|
||||
"xxbaryy",
|
||||
sizeof("xxbaryy") - 1,
|
||||
REG_NOTBOL | REG_NOTEOL,
|
||||
REG_OK,
|
||||
TRE_LITERAL_OPT_CONTAINS
|
||||
},
|
||||
{
|
||||
"prefix basic",
|
||||
"^(foo|bar|baz)",
|
||||
sizeof("^(foo|bar|baz)") - 1,
|
||||
REG_EXTENDED | REG_NOSUB,
|
||||
"barrier",
|
||||
sizeof("barrier") - 1,
|
||||
0,
|
||||
REG_OK,
|
||||
TRE_LITERAL_OPT_PREFIX
|
||||
},
|
||||
{
|
||||
"prefix respects REG_NOTBOL",
|
||||
"^(foo|bar|baz)",
|
||||
sizeof("^(foo|bar|baz)") - 1,
|
||||
REG_EXTENDED | REG_NOSUB,
|
||||
"barrier",
|
||||
sizeof("barrier") - 1,
|
||||
REG_NOTBOL,
|
||||
REG_NOMATCH,
|
||||
TRE_LITERAL_OPT_PREFIX
|
||||
},
|
||||
{
|
||||
"suffix basic",
|
||||
"(foo|bar|baz)$",
|
||||
sizeof("(foo|bar|baz)$") - 1,
|
||||
REG_EXTENDED | REG_NOSUB,
|
||||
"crowbar",
|
||||
sizeof("crowbar") - 1,
|
||||
0,
|
||||
REG_OK,
|
||||
TRE_LITERAL_OPT_SUFFIX
|
||||
},
|
||||
{
|
||||
"suffix respects REG_NOTEOL",
|
||||
"(foo|bar|baz)$",
|
||||
sizeof("(foo|bar|baz)$") - 1,
|
||||
REG_EXTENDED | REG_NOSUB,
|
||||
"crowbar",
|
||||
sizeof("crowbar") - 1,
|
||||
REG_NOTEOL,
|
||||
REG_NOMATCH,
|
||||
TRE_LITERAL_OPT_SUFFIX
|
||||
},
|
||||
{
|
||||
"exact basic",
|
||||
"^(foo|bar|baz)$",
|
||||
sizeof("^(foo|bar|baz)$") - 1,
|
||||
REG_EXTENDED | REG_NOSUB,
|
||||
"bar",
|
||||
sizeof("bar") - 1,
|
||||
0,
|
||||
REG_OK,
|
||||
TRE_LITERAL_OPT_EXACT
|
||||
},
|
||||
{
|
||||
"exact respects REG_NOTBOL",
|
||||
"^(foo|bar|baz)$",
|
||||
sizeof("^(foo|bar|baz)$") - 1,
|
||||
REG_EXTENDED | REG_NOSUB,
|
||||
"bar",
|
||||
sizeof("bar") - 1,
|
||||
REG_NOTBOL,
|
||||
REG_NOMATCH,
|
||||
TRE_LITERAL_OPT_EXACT
|
||||
},
|
||||
{
|
||||
"exact respects REG_NOTEOL",
|
||||
"^(foo|bar|baz)$",
|
||||
sizeof("^(foo|bar|baz)$") - 1,
|
||||
REG_EXTENDED | REG_NOSUB,
|
||||
"bar",
|
||||
sizeof("bar") - 1,
|
||||
REG_NOTEOL,
|
||||
REG_NOMATCH,
|
||||
TRE_LITERAL_OPT_EXACT
|
||||
},
|
||||
{
|
||||
"empty alternation disables optimization",
|
||||
"(|foo|bar)",
|
||||
sizeof("(|foo|bar)") - 1,
|
||||
REG_EXTENDED | REG_NOSUB,
|
||||
"",
|
||||
0,
|
||||
0,
|
||||
REG_OK,
|
||||
TRE_LITERAL_OPT_NONE
|
||||
},
|
||||
{
|
||||
"inline flag disable stays generic",
|
||||
"foo(?-i:zap)zot",
|
||||
sizeof("foo(?-i:zap)zot") - 1,
|
||||
REG_EXTENDED | REG_ICASE | REG_NOSUB,
|
||||
"FoOzApZOt",
|
||||
sizeof("FoOzApZOt") - 1,
|
||||
0,
|
||||
REG_NOMATCH,
|
||||
TRE_LITERAL_OPT_NONE
|
||||
},
|
||||
{
|
||||
"inline flag disable still matches exact scoped bytes",
|
||||
"foo(?-i:zap)zot",
|
||||
sizeof("foo(?-i:zap)zot") - 1,
|
||||
REG_EXTENDED | REG_ICASE | REG_NOSUB,
|
||||
"FoOzapZOt",
|
||||
sizeof("FoOzapZOt") - 1,
|
||||
0,
|
||||
REG_OK,
|
||||
TRE_LITERAL_OPT_NONE
|
||||
},
|
||||
{
|
||||
"nocase non-ascii bytes stay in sync",
|
||||
nonascii_pattern,
|
||||
sizeof(nonascii_pattern),
|
||||
REG_EXTENDED | REG_ICASE | REG_NOSUB,
|
||||
nonascii_haystack,
|
||||
sizeof(nonascii_haystack),
|
||||
0,
|
||||
RC_ANY,
|
||||
TRE_LITERAL_OPT_CONTAINS
|
||||
}
|
||||
};
|
||||
size_t i;
|
||||
int failures = 0;
|
||||
|
||||
setlocale(LC_CTYPE, "en_US.ISO-8859-1");
|
||||
|
||||
for (i = 0; i < elementsof(cases); i++)
|
||||
failures += run_case(&cases[i]);
|
||||
|
||||
return failures;
|
||||
}
|
||||
85
deps/tre/tests/test-malformed-regn.c
vendored
Normal file
85
deps/tre/tests/test-malformed-regn.c
vendored
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
test-malformed-regn.c - Verify exact-length edge-case regexps compile or fail
|
||||
cleanly both with and without a trailing NUL byte.
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "tre.h"
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
const char *pattern;
|
||||
int expected_err;
|
||||
} malformed_case_t;
|
||||
|
||||
static int
|
||||
run_case(const malformed_case_t *tc, int nul_terminated)
|
||||
{
|
||||
regex_t preg;
|
||||
size_t len = strlen(tc->pattern);
|
||||
size_t alloc_len = len + (nul_terminated ? 1 : 0);
|
||||
char *pattern = malloc(alloc_len ? alloc_len : 1);
|
||||
int errcode;
|
||||
|
||||
if (pattern == NULL)
|
||||
{
|
||||
fprintf(stderr, "%s: out of memory\n", tc->name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (len > 0)
|
||||
memcpy(pattern, tc->pattern, len);
|
||||
if (nul_terminated)
|
||||
pattern[len] = '\0';
|
||||
|
||||
memset(&preg, 0, sizeof(preg));
|
||||
errcode = tre_regncompb(&preg, pattern, len, REG_EXTENDED | REG_NOSUB);
|
||||
if (errcode == REG_OK)
|
||||
tre_regfree(&preg);
|
||||
|
||||
free(pattern);
|
||||
|
||||
if (errcode != tc->expected_err)
|
||||
{
|
||||
char errbuf[128];
|
||||
memset(&preg, 0, sizeof(preg));
|
||||
tre_regerror(errcode, &preg, errbuf, sizeof(errbuf));
|
||||
fprintf(stderr, "%s (%s): got %d (%s), expected %d\n",
|
||||
tc->name, nul_terminated ? "nul" : "exact",
|
||||
errcode, errbuf, tc->expected_err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
static const malformed_case_t cases[] = {
|
||||
{ "open paren", "(", REG_EPAREN },
|
||||
{ "open bracket", "[", REG_EBRACK },
|
||||
{ "unterminated comment", "(?#", REG_BADPAT },
|
||||
{ "unterminated inline flags", "(?i", REG_BADPAT },
|
||||
{ "short hex escape", "\\x", REG_OK },
|
||||
{ "unterminated wide hex", "\\x{", REG_EBRACE },
|
||||
{ "empty wide hex", "\\x{}", REG_OK }
|
||||
};
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < sizeof(cases) / sizeof(*cases); i++)
|
||||
{
|
||||
if (run_case(&cases[i], 0))
|
||||
return 1;
|
||||
if (run_case(&cases[i], 1))
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
192
deps/tre/tests/test-str-source.c
vendored
Normal file
192
deps/tre/tests/test-str-source.c
vendored
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
test-str-source.c - Sample program for using tre_reguexec()
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
/* look for getopt in order to use a -o option for output. */
|
||||
#if defined(HAVE_UNISTD_H)
|
||||
#include <unistd.h>
|
||||
#elif defined(HAVE_GETOPT_H)
|
||||
#include <getopt.h>
|
||||
#endif
|
||||
|
||||
#include "tre-internal.h"
|
||||
|
||||
static FILE *outf = NULL;
|
||||
|
||||
/* Context structure for the tre_str_source wrappers. */
|
||||
typedef struct {
|
||||
/* Our string. */
|
||||
const char *str;
|
||||
/* Current position in the string. */
|
||||
size_t pos;
|
||||
} str_handler_ctx;
|
||||
|
||||
/* The get_next_char() handler. Sets `c' to the value of the next character,
|
||||
and increases `pos_add' by the number of bytes read. Returns 1 if the
|
||||
string has ended, 0 if there are more characters. */
|
||||
static int
|
||||
str_handler_get_next(tre_char_t *c, unsigned int *pos_add, void *context)
|
||||
{
|
||||
str_handler_ctx *ctx = context;
|
||||
unsigned char ch = ctx->str[ctx->pos];
|
||||
|
||||
#ifdef TRE_DEBUG
|
||||
fprintf(outf, "str[%lu] = %d\n", (unsigned long)ctx->pos, ch);
|
||||
#endif /* TRE_DEBUG */
|
||||
*c = ch;
|
||||
if (ch)
|
||||
ctx->pos++;
|
||||
*pos_add = 1;
|
||||
|
||||
return ch == '\0';
|
||||
}
|
||||
|
||||
/* The rewind() handler. Resets the current position in the input string. */
|
||||
static void
|
||||
str_handler_rewind(size_t pos, void *context)
|
||||
{
|
||||
str_handler_ctx *ctx = context;
|
||||
|
||||
#ifdef TRE_DEBUG
|
||||
fprintf(outf, "rewind to %lu\n", (unsigned long)pos);
|
||||
#endif /* TRE_DEBUG */
|
||||
ctx->pos = pos;
|
||||
}
|
||||
|
||||
/* The compare() handler. Compares two substrings in the input and returns
|
||||
0 if the substrings are equal, and a nonzero value if not. */
|
||||
static int
|
||||
str_handler_compare(size_t pos1, size_t pos2, size_t len, void *context)
|
||||
{
|
||||
str_handler_ctx *ctx = context;
|
||||
#ifdef TRE_DEBUG
|
||||
fprintf(outf, "comparing %lu-%lu and %lu-%lu\n",
|
||||
(unsigned long)pos1, (unsigned long)pos1 + len,
|
||||
(unsigned long)pos2, (unsigned long)pos2 + len);
|
||||
#endif /* TRE_DEBUG */
|
||||
return strncmp(ctx->str + pos1, ctx->str + pos2, len);
|
||||
}
|
||||
|
||||
/* Creates a tre_str_source wrapper around the string `str'. Returns the
|
||||
tre_str_source object or NULL if out of memory. */
|
||||
static tre_str_source *
|
||||
make_str_source(const char *str)
|
||||
{
|
||||
tre_str_source *s;
|
||||
str_handler_ctx *ctx;
|
||||
|
||||
s = calloc(1, sizeof(*s));
|
||||
if (!s)
|
||||
return NULL;
|
||||
|
||||
ctx = malloc(sizeof(str_handler_ctx));
|
||||
if (!ctx)
|
||||
{
|
||||
free(s);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ctx->str = str;
|
||||
ctx->pos = 0;
|
||||
s->context = ctx;
|
||||
s->get_next_char = str_handler_get_next;
|
||||
s->rewind = str_handler_rewind;
|
||||
s->compare = str_handler_compare;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Frees the memory allocated for `s'. */
|
||||
static void
|
||||
free_str_source(tre_str_source *s)
|
||||
{
|
||||
free(s->context);
|
||||
free(s);
|
||||
}
|
||||
|
||||
/* Run one test with tre_reguexec. Returns 1 if the regex matches, 0 if
|
||||
it doesn't, and -1 if an error occurs. */
|
||||
static int
|
||||
test_reguexec(const char *str, const char *regex)
|
||||
{
|
||||
regex_t preg;
|
||||
tre_str_source *source;
|
||||
regmatch_t pmatch[5];
|
||||
int ret;
|
||||
|
||||
if ((source = make_str_source(str)) == NULL)
|
||||
{
|
||||
fprintf(stderr, "Out of memory\n");
|
||||
ret = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tre_regcomp(&preg, regex, REG_EXTENDED) != REG_OK)
|
||||
{
|
||||
fprintf(stderr, "Failed to compile /%s/\n", regex);
|
||||
ret = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tre_reguexec(&preg, source, elementsof(pmatch), pmatch, 0) == 0)
|
||||
{
|
||||
fprintf(outf, "Match: /%s/ matches \"%.*s\" in \"%s\"\n", regex,
|
||||
(int)(pmatch[0].rm_eo - pmatch[0].rm_so),
|
||||
str + pmatch[0].rm_so, str);
|
||||
ret = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(outf, "No match: /%s/ in \"%s\"\n", regex, str);
|
||||
ret = 0;
|
||||
}
|
||||
tre_regfree(&preg);
|
||||
}
|
||||
free_str_source(source);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
int ret = 0;
|
||||
outf = stdout;
|
||||
#if defined(HAVE_UNISTD_H) || defined(HAVE_GETOPT_H)
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "o:")) != EOF)
|
||||
{
|
||||
switch (opt)
|
||||
{
|
||||
case 'o':
|
||||
if ((outf = fopen(optarg, "w")) == NULL)
|
||||
{
|
||||
perror(optarg);
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* getopt() will have printed an error message already */
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
ret += test_reguexec("xfoofofoofoo", "(foo)\\1") != 1;
|
||||
ret += test_reguexec("catcat", "(cat|dog)\\1") != 1;
|
||||
ret += test_reguexec("catdog", "(cat|dog)\\1") != 0;
|
||||
ret += test_reguexec("dogdog", "(cat|dog)\\1") != 1;
|
||||
ret += test_reguexec("dogcat", "(cat|dog)\\1") != 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
SRC_DIR = src
|
||||
MODULE_VERSION = v8.7.90
|
||||
MODULE_VERSION = v8.7.91
|
||||
MODULE_REPO = https://github.com/redisbloom/redisbloom
|
||||
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/redisbloom.so
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
SRC_DIR = src
|
||||
MODULE_VERSION = v8.7.90
|
||||
MODULE_VERSION = v8.7.91
|
||||
MODULE_REPO = https://github.com/redisearch/redisearch
|
||||
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/search-community/redisearch.so
|
||||
|
||||
|
|
@ -7,5 +7,10 @@ TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/search-community/redisearch.so
|
|||
LTO ?= 1
|
||||
export LTO
|
||||
|
||||
# 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.90
|
||||
MODULE_VERSION = v8.7.91
|
||||
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.90
|
||||
MODULE_VERSION = v8.7.91
|
||||
MODULE_REPO = https://github.com/redistimeseries/redistimeseries
|
||||
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/redistimeseries.so
|
||||
|
||||
|
|
|
|||
129
modules/vector-sets/tests/dimension_max_limit.py
Normal file
129
modules/vector-sets/tests/dimension_max_limit.py
Normal file
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
37
redis.conf
37
redis.conf
|
|
@ -2044,21 +2044,21 @@ 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)
|
||||
# r rate limit event
|
||||
# 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$lshzxetd, so that the "AKE" string means all the events
|
||||
# except key-miss, new key, overwritten, type-changed and rate-limit.
|
||||
# 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
|
||||
# of zero or multiple characters. The empty string means that notifications
|
||||
|
|
@ -2187,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)
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ endif
|
|||
ifneq ($(OPTIMIZATION),-O0)
|
||||
OPTIMIZATION+=-fno-omit-frame-pointer
|
||||
endif
|
||||
DEPENDENCY_TARGETS=hiredis linenoise lua hdr_histogram fpconv xxhash
|
||||
DEPENDENCY_TARGETS=hiredis linenoise lua hdr_histogram fpconv xxhash tre
|
||||
NODEPS:=clean distclean
|
||||
|
||||
# Default settings
|
||||
|
|
@ -384,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 vector.o fast_float_strtod.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)
|
||||
|
|
@ -444,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/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,7 +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. */
|
||||
};
|
||||
|
||||
|
|
|
|||
116
src/aof.c
116
src/aof.c
|
|
@ -2467,6 +2467,7 @@ 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);
|
||||
|
|
@ -2478,6 +2479,7 @@ int rewriteGCRAObject(rio *r, robj *key, robj *o) {
|
|||
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.
|
||||
|
|
@ -2515,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) {
|
||||
|
|
@ -2534,8 +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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -804,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) {
|
||||
|
|
|
|||
613
src/commands.def
613
src/commands.def
|
|
@ -24,13 +24,545 @@ const char *COMMAND_GROUP_STR[] = {
|
|||
"geo",
|
||||
"stream",
|
||||
"bitmap",
|
||||
"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
|
||||
|
|
@ -5380,59 +5912,6 @@ struct COMMAND_ARG UNSUBSCRIBE_Args[] = {
|
|||
{MAKE_ARG("channel",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE,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("tokens-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,"TOKENS",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL)},
|
||||
};
|
||||
|
||||
/********** GCRASETVALUE ********************/
|
||||
|
||||
#ifndef SKIP_CMD_HISTORY_TABLE
|
||||
/* GCRASETVALUE history */
|
||||
#define GCRASETVALUE_History NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_TIPS_TABLE
|
||||
/* GCRASETVALUE tips */
|
||||
#define GCRASETVALUE_Tips NULL
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_KEY_SPECS_TABLE
|
||||
/* GCRASETVALUE key specs */
|
||||
keySpec GCRASETVALUE_Keyspecs[1] = {
|
||||
{NULL,CMD_KEY_OW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}
|
||||
};
|
||||
#endif
|
||||
|
||||
/* GCRASETVALUE argument table */
|
||||
struct COMMAND_ARG GCRASETVALUE_Args[] = {
|
||||
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("tat",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
};
|
||||
|
||||
/********** EVAL ********************/
|
||||
|
||||
#ifndef SKIP_CMD_HISTORY_TABLE
|
||||
|
|
@ -11876,6 +12355,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},
|
||||
|
|
@ -11998,18 +12496,15 @@ 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},
|
||||
/* rate_limit */
|
||||
{MAKE_CMD("gcra","Rate limit via GCRA (Generic Cell Rate Algorithm).","O(1)","8.8.0",CMD_DOC_NONE,NULL,NULL,"rate_limit",COMMAND_GROUP_RATE_LIMIT,GCRA_History,0,GCRA_Tips,0,gcraCommand,-5,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_RATE_LIMIT,GCRA_Keyspecs,1,NULL,5),.args=GCRA_Args},
|
||||
{MAKE_CMD("gcrasetvalue","An internal command for recording a GCRA TAT value during AOF rewrite and replication.","O(1)","8.8.0",CMD_DOC_NONE,NULL,NULL,"rate_limit",COMMAND_GROUP_RATE_LIMIT,GCRASETVALUE_History,0,GCRASETVALUE_Tips,0,gcraSetValueCommand,3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_RATE_LIMIT,GCRASETVALUE_Keyspecs,1,NULL,2),.args=GCRASETVALUE_Args},
|
||||
/* scripting */
|
||||
{MAKE_CMD("eval","Executes a server-side Lua script.","Depends on the script that is executed.","2.6.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,EVAL_History,0,EVAL_Tips,0,evalCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_MAY_REPLICATE|CMD_NO_MANDATORY_KEYS|CMD_STALE,ACL_CATEGORY_SCRIPTING,EVAL_Keyspecs,1,evalGetKeys,4),.args=EVAL_Args},
|
||||
{MAKE_CMD("evalsha","Executes a server-side Lua script by SHA1 digest.","Depends on the script that is executed.","2.6.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,EVALSHA_History,0,EVALSHA_Tips,0,evalShaCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_MAY_REPLICATE|CMD_NO_MANDATORY_KEYS|CMD_STALE,ACL_CATEGORY_SCRIPTING,EVALSHA_Keyspecs,1,evalGetKeys,4),.args=EVALSHA_Args},
|
||||
|
|
|
|||
48
src/commands/arcount.json
Normal file
48
src/commands/arcount.json
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"ARCOUNT": {
|
||||
"summary": "Returns the number of non-empty elements in an array.",
|
||||
"complexity": "O(1)",
|
||||
"group": "array",
|
||||
"since": "8.8.0",
|
||||
"arity": 2,
|
||||
"function": "arcountCommand",
|
||||
"command_flags": [
|
||||
"READONLY",
|
||||
"FAST"
|
||||
],
|
||||
"acl_categories": [
|
||||
"ARRAY"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"RO",
|
||||
"ACCESS"
|
||||
],
|
||||
"begin_search": {
|
||||
"index": {
|
||||
"pos": 1
|
||||
}
|
||||
},
|
||||
"find_keys": {
|
||||
"range": {
|
||||
"lastkey": 0,
|
||||
"step": 1,
|
||||
"limit": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"description": "The number of non-empty elements, or 0 if key does not exist.",
|
||||
"type": "integer"
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key",
|
||||
"key_spec_index": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
53
src/commands/ardel.json
Normal file
53
src/commands/ardel.json
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"ARDEL": {
|
||||
"summary": "Deletes elements at the specified indices in an array.",
|
||||
"complexity": "O(N) where N is the number of indices to delete",
|
||||
"group": "array",
|
||||
"since": "8.8.0",
|
||||
"arity": -3,
|
||||
"function": "ardelCommand",
|
||||
"command_flags": [
|
||||
"WRITE",
|
||||
"FAST"
|
||||
],
|
||||
"acl_categories": [
|
||||
"ARRAY"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"RW",
|
||||
"DELETE"
|
||||
],
|
||||
"begin_search": {
|
||||
"index": {
|
||||
"pos": 1
|
||||
}
|
||||
},
|
||||
"find_keys": {
|
||||
"range": {
|
||||
"lastkey": 0,
|
||||
"step": 1,
|
||||
"limit": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"description": "Number of elements deleted.",
|
||||
"type": "integer"
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key",
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "index",
|
||||
"type": "integer",
|
||||
"multiple": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
62
src/commands/ardelrange.json
Normal file
62
src/commands/ardelrange.json
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
{
|
||||
"ARDELRANGE": {
|
||||
"summary": "Deletes elements in one or more ranges.",
|
||||
"complexity": "Proportional to the number of existing elements / slices touched, not to the numeric span of the requested ranges",
|
||||
"group": "array",
|
||||
"since": "8.8.0",
|
||||
"arity": -4,
|
||||
"function": "ardelrangeCommand",
|
||||
"command_flags": [
|
||||
"WRITE"
|
||||
],
|
||||
"acl_categories": [
|
||||
"ARRAY"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"RW",
|
||||
"DELETE"
|
||||
],
|
||||
"begin_search": {
|
||||
"index": {
|
||||
"pos": 1
|
||||
}
|
||||
},
|
||||
"find_keys": {
|
||||
"range": {
|
||||
"lastkey": 0,
|
||||
"step": 1,
|
||||
"limit": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"description": "Number of elements deleted.",
|
||||
"type": "integer"
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key",
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "range",
|
||||
"type": "block",
|
||||
"multiple": true,
|
||||
"arguments": [
|
||||
{
|
||||
"name": "start",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "end",
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
60
src/commands/arget.json
Normal file
60
src/commands/arget.json
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
"ARGET": {
|
||||
"summary": "Gets the value at an index in an array.",
|
||||
"complexity": "O(1)",
|
||||
"group": "array",
|
||||
"since": "8.8.0",
|
||||
"arity": 3,
|
||||
"function": "argetCommand",
|
||||
"command_flags": [
|
||||
"READONLY",
|
||||
"FAST"
|
||||
],
|
||||
"acl_categories": [
|
||||
"ARRAY"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"RO",
|
||||
"ACCESS"
|
||||
],
|
||||
"begin_search": {
|
||||
"index": {
|
||||
"pos": 1
|
||||
}
|
||||
},
|
||||
"find_keys": {
|
||||
"range": {
|
||||
"lastkey": 0,
|
||||
"step": 1,
|
||||
"limit": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "The value at the given index.",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "Null reply if key or index does not exist.",
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key",
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "index",
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
64
src/commands/argetrange.json
Normal file
64
src/commands/argetrange.json
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
"ARGETRANGE": {
|
||||
"summary": "Gets values in a range of indices.",
|
||||
"complexity": "O(N) where N is the range length",
|
||||
"group": "array",
|
||||
"since": "8.8.0",
|
||||
"arity": 4,
|
||||
"function": "argetrangeCommand",
|
||||
"command_flags": [
|
||||
"READONLY"
|
||||
],
|
||||
"acl_categories": [
|
||||
"ARRAY"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"RO",
|
||||
"ACCESS"
|
||||
],
|
||||
"begin_search": {
|
||||
"index": {
|
||||
"pos": 1
|
||||
}
|
||||
},
|
||||
"find_keys": {
|
||||
"range": {
|
||||
"lastkey": 0,
|
||||
"step": 1,
|
||||
"limit": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key",
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "start",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "end",
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
182
src/commands/argrep.json
Normal file
182
src/commands/argrep.json
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
{
|
||||
"ARGREP": {
|
||||
"summary": "Searches array elements in a range using textual predicates.",
|
||||
"complexity": "O(P * C) where P is the number of visited positions in touched slices and C is the cost of evaluating the predicates on one existing element.",
|
||||
"group": "array",
|
||||
"since": "8.8.0",
|
||||
"arity": -6,
|
||||
"function": "argrepCommand",
|
||||
"command_flags": [
|
||||
"READONLY"
|
||||
],
|
||||
"acl_categories": [
|
||||
"ARRAY"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"RO",
|
||||
"ACCESS"
|
||||
],
|
||||
"begin_search": {
|
||||
"index": {
|
||||
"pos": 1
|
||||
}
|
||||
},
|
||||
"find_keys": {
|
||||
"range": {
|
||||
"lastkey": 0,
|
||||
"step": 1,
|
||||
"limit": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "Array of matching indexes.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"description": "Index of a matching element"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Array of [index, value] pairs. Returned in case `WITHVALUES` was used.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "array",
|
||||
"minItems": 2,
|
||||
"maxItems": 2,
|
||||
"items": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Index of a matching element"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Value at that index"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key",
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "start",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "end",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "predicate",
|
||||
"type": "oneof",
|
||||
"multiple": true,
|
||||
"arguments": [
|
||||
{
|
||||
"name": "exact",
|
||||
"type": "block",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "exact",
|
||||
"type": "pure-token",
|
||||
"token": "EXACT"
|
||||
},
|
||||
{
|
||||
"name": "string",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "match",
|
||||
"type": "block",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "match",
|
||||
"type": "pure-token",
|
||||
"token": "MATCH"
|
||||
},
|
||||
{
|
||||
"name": "string",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "glob",
|
||||
"type": "block",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "glob",
|
||||
"type": "pure-token",
|
||||
"token": "GLOB"
|
||||
},
|
||||
{
|
||||
"name": "pattern",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "re",
|
||||
"type": "block",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "re",
|
||||
"type": "pure-token",
|
||||
"token": "RE"
|
||||
},
|
||||
{
|
||||
"name": "pattern",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "options",
|
||||
"type": "oneof",
|
||||
"optional": true,
|
||||
"multiple": true,
|
||||
"arguments": [
|
||||
{
|
||||
"name": "and",
|
||||
"type": "pure-token",
|
||||
"token": "AND"
|
||||
},
|
||||
{
|
||||
"name": "or",
|
||||
"type": "pure-token",
|
||||
"token": "OR"
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"type": "integer",
|
||||
"token": "LIMIT"
|
||||
},
|
||||
{
|
||||
"name": "withvalues",
|
||||
"type": "pure-token",
|
||||
"token": "WITHVALUES"
|
||||
},
|
||||
{
|
||||
"name": "nocase",
|
||||
"type": "pure-token",
|
||||
"token": "NOCASE"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
103
src/commands/arinfo.json
Normal file
103
src/commands/arinfo.json
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
{
|
||||
"ARINFO": {
|
||||
"summary": "Returns metadata about an array.",
|
||||
"complexity": "O(1), or O(N) with FULL option where N is the number of slices.",
|
||||
"group": "array",
|
||||
"since": "8.8.0",
|
||||
"arity": -2,
|
||||
"function": "arinfoCommand",
|
||||
"command_flags": [
|
||||
"READONLY"
|
||||
],
|
||||
"acl_categories": [
|
||||
"ARRAY"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"RO",
|
||||
"ACCESS"
|
||||
],
|
||||
"begin_search": {
|
||||
"index": {
|
||||
"pos": 1
|
||||
}
|
||||
},
|
||||
"find_keys": {
|
||||
"range": {
|
||||
"lastkey": 0,
|
||||
"step": 1,
|
||||
"limit": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"count": {
|
||||
"type": "integer",
|
||||
"description": "Total number of non-empty elements."
|
||||
},
|
||||
"len": {
|
||||
"type": "integer",
|
||||
"description": "Logical length (highest index + 1)."
|
||||
},
|
||||
"next-insert-index": {
|
||||
"type": "integer",
|
||||
"description": "Index the next ARINSERT would use, or 0 if unset/exhausted."
|
||||
},
|
||||
"slices": {
|
||||
"type": "integer",
|
||||
"description": "Number of allocated slices."
|
||||
},
|
||||
"directory-size": {
|
||||
"type": "integer",
|
||||
"description": "Directory allocation capacity (flat dir_alloc or superdir sdir_cap)."
|
||||
},
|
||||
"super-dir-entries": {
|
||||
"type": "integer",
|
||||
"description": "Number of super-directory entries (0 if not in superdir mode)."
|
||||
},
|
||||
"slice-size": {
|
||||
"type": "integer",
|
||||
"description": "Configured slice size."
|
||||
},
|
||||
"dense-slices": {
|
||||
"type": "integer",
|
||||
"description": "Number of dense slices (FULL only)."
|
||||
},
|
||||
"sparse-slices": {
|
||||
"type": "integer",
|
||||
"description": "Number of sparse slices (FULL only)."
|
||||
},
|
||||
"avg-dense-size": {
|
||||
"type": "number",
|
||||
"description": "Average allocation size of dense slices (FULL only)."
|
||||
},
|
||||
"avg-dense-fill": {
|
||||
"type": "number",
|
||||
"description": "Average fill rate of dense slices (FULL only)."
|
||||
},
|
||||
"avg-sparse-size": {
|
||||
"type": "number",
|
||||
"description": "Average capacity of sparse slices (FULL only)."
|
||||
}
|
||||
}
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key",
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "full",
|
||||
"type": "pure-token",
|
||||
"token": "FULL",
|
||||
"optional": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
54
src/commands/arinsert.json
Normal file
54
src/commands/arinsert.json
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"ARINSERT": {
|
||||
"summary": "Inserts one or more values at consecutive indices.",
|
||||
"complexity": "O(N) where N is the number of values",
|
||||
"group": "array",
|
||||
"since": "8.8.0",
|
||||
"arity": -3,
|
||||
"function": "arinsertCommand",
|
||||
"command_flags": [
|
||||
"WRITE",
|
||||
"DENYOOM",
|
||||
"FAST"
|
||||
],
|
||||
"acl_categories": [
|
||||
"ARRAY"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"RW",
|
||||
"UPDATE"
|
||||
],
|
||||
"begin_search": {
|
||||
"index": {
|
||||
"pos": 1
|
||||
}
|
||||
},
|
||||
"find_keys": {
|
||||
"range": {
|
||||
"lastkey": 0,
|
||||
"step": 1,
|
||||
"limit": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"description": "The last index where a value was inserted.",
|
||||
"type": "integer"
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key",
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
66
src/commands/arlastitems.json
Normal file
66
src/commands/arlastitems.json
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
{
|
||||
"ARLASTITEMS": {
|
||||
"summary": "Returns the most recently inserted elements.",
|
||||
"complexity": "O(N) where N is the count",
|
||||
"group": "array",
|
||||
"since": "8.8.0",
|
||||
"arity": -3,
|
||||
"function": "arlastitemsCommand",
|
||||
"command_flags": [
|
||||
"READONLY"
|
||||
],
|
||||
"acl_categories": [
|
||||
"ARRAY"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"RO",
|
||||
"ACCESS"
|
||||
],
|
||||
"begin_search": {
|
||||
"index": {
|
||||
"pos": 1
|
||||
}
|
||||
},
|
||||
"find_keys": {
|
||||
"range": {
|
||||
"lastkey": 0,
|
||||
"step": 1,
|
||||
"limit": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key",
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "count",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "rev",
|
||||
"type": "pure-token",
|
||||
"token": "REV",
|
||||
"optional": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
48
src/commands/arlen.json
Normal file
48
src/commands/arlen.json
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"ARLEN": {
|
||||
"summary": "Returns the length of an array (max index + 1).",
|
||||
"complexity": "O(1)",
|
||||
"group": "array",
|
||||
"since": "8.8.0",
|
||||
"arity": 2,
|
||||
"function": "arlenCommand",
|
||||
"command_flags": [
|
||||
"READONLY",
|
||||
"FAST"
|
||||
],
|
||||
"acl_categories": [
|
||||
"ARRAY"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"RO",
|
||||
"ACCESS"
|
||||
],
|
||||
"begin_search": {
|
||||
"index": {
|
||||
"pos": 1
|
||||
}
|
||||
},
|
||||
"find_keys": {
|
||||
"range": {
|
||||
"lastkey": 0,
|
||||
"step": 1,
|
||||
"limit": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"description": "The length of the array (max index + 1), or 0 if key does not exist.",
|
||||
"type": "integer"
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key",
|
||||
"key_spec_index": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
62
src/commands/armget.json
Normal file
62
src/commands/armget.json
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
{
|
||||
"ARMGET": {
|
||||
"summary": "Gets values at multiple indices in an array.",
|
||||
"complexity": "O(N) where N is the number of indices",
|
||||
"group": "array",
|
||||
"since": "8.8.0",
|
||||
"arity": -3,
|
||||
"function": "armgetCommand",
|
||||
"command_flags": [
|
||||
"READONLY",
|
||||
"FAST"
|
||||
],
|
||||
"acl_categories": [
|
||||
"ARRAY"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"RO",
|
||||
"ACCESS"
|
||||
],
|
||||
"begin_search": {
|
||||
"index": {
|
||||
"pos": 1
|
||||
}
|
||||
},
|
||||
"find_keys": {
|
||||
"range": {
|
||||
"lastkey": 0,
|
||||
"step": 1,
|
||||
"limit": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key",
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "index",
|
||||
"type": "integer",
|
||||
"multiple": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
64
src/commands/armset.json
Normal file
64
src/commands/armset.json
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
"ARMSET": {
|
||||
"summary": "Sets multiple index-value pairs in an array.",
|
||||
"complexity": "O(N) where N is the number of pairs",
|
||||
"group": "array",
|
||||
"since": "8.8.0",
|
||||
"arity": -4,
|
||||
"function": "armsetCommand",
|
||||
"command_flags": [
|
||||
"WRITE",
|
||||
"DENYOOM",
|
||||
"FAST"
|
||||
],
|
||||
"acl_categories": [
|
||||
"ARRAY"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"RW",
|
||||
"UPDATE"
|
||||
],
|
||||
"begin_search": {
|
||||
"index": {
|
||||
"pos": 1
|
||||
}
|
||||
},
|
||||
"find_keys": {
|
||||
"range": {
|
||||
"lastkey": 0,
|
||||
"step": 1,
|
||||
"limit": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"description": "Number of new slots that were set (previously empty).",
|
||||
"type": "integer"
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key",
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "data",
|
||||
"type": "block",
|
||||
"multiple": true,
|
||||
"arguments": [
|
||||
{
|
||||
"name": "index",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
56
src/commands/arnext.json
Normal file
56
src/commands/arnext.json
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
{
|
||||
"ARNEXT": {
|
||||
"summary": "Returns the next index ARINSERT would use.",
|
||||
"complexity": "O(1)",
|
||||
"group": "array",
|
||||
"since": "8.8.0",
|
||||
"arity": 2,
|
||||
"function": "arnextCommand",
|
||||
"command_flags": [
|
||||
"READONLY",
|
||||
"FAST"
|
||||
],
|
||||
"acl_categories": [
|
||||
"ARRAY"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"RO",
|
||||
"ACCESS"
|
||||
],
|
||||
"begin_search": {
|
||||
"index": {
|
||||
"pos": 1
|
||||
}
|
||||
},
|
||||
"find_keys": {
|
||||
"range": {
|
||||
"lastkey": 0,
|
||||
"step": 1,
|
||||
"limit": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "The next index ARINSERT would use. Returns 0 for missing keys or when no insert happened yet.",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"description": "Null when the insertion cursor is exhausted (next insert would overflow).",
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key",
|
||||
"key_spec_index": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
123
src/commands/arop.json
Normal file
123
src/commands/arop.json
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
{
|
||||
"AROP": {
|
||||
"summary": "Performs aggregate operations on array elements in a range.",
|
||||
"complexity": "O(P) where P is visited positions in touched slices (dense scanned slots + sparse entries), with worst-case O(|end-start|+1) and typical case close to O(N), where N is the number of existing elements in range.",
|
||||
"group": "array",
|
||||
"since": "8.8.0",
|
||||
"arity": -5,
|
||||
"function": "aropCommand",
|
||||
"command_flags": [
|
||||
"READONLY"
|
||||
],
|
||||
"acl_categories": [
|
||||
"ARRAY"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"RO",
|
||||
"ACCESS"
|
||||
],
|
||||
"begin_search": {
|
||||
"index": {
|
||||
"pos": 1
|
||||
}
|
||||
},
|
||||
"find_keys": {
|
||||
"range": {
|
||||
"lastkey": 0,
|
||||
"step": 1,
|
||||
"limit": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Result of the operation.",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "Integer result for MATCH, USED, AND, OR, XOR.",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"description": "Null if no elements match the operation.",
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key",
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "start",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "end",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "operation",
|
||||
"type": "oneof",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "sum",
|
||||
"type": "pure-token",
|
||||
"token": "SUM"
|
||||
},
|
||||
{
|
||||
"name": "min",
|
||||
"type": "pure-token",
|
||||
"token": "MIN"
|
||||
},
|
||||
{
|
||||
"name": "max",
|
||||
"type": "pure-token",
|
||||
"token": "MAX"
|
||||
},
|
||||
{
|
||||
"name": "and",
|
||||
"type": "pure-token",
|
||||
"token": "AND"
|
||||
},
|
||||
{
|
||||
"name": "or",
|
||||
"type": "pure-token",
|
||||
"token": "OR"
|
||||
},
|
||||
{
|
||||
"name": "xor",
|
||||
"type": "pure-token",
|
||||
"token": "XOR"
|
||||
},
|
||||
{
|
||||
"name": "match",
|
||||
"type": "block",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "match",
|
||||
"type": "pure-token",
|
||||
"token": "MATCH"
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "used",
|
||||
"type": "pure-token",
|
||||
"token": "USED"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
57
src/commands/arring.json
Normal file
57
src/commands/arring.json
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"ARRING": {
|
||||
"summary": "Inserts values into a ring buffer of specified size, wrapping and truncating as needed.",
|
||||
"complexity": "O(M) normally, O(N+M) on ring resize, where N is the maximum of the old and new ring size and M is the number of inserted values",
|
||||
"group": "array",
|
||||
"since": "8.8.0",
|
||||
"arity": -4,
|
||||
"function": "arringCommand",
|
||||
"command_flags": [
|
||||
"WRITE",
|
||||
"DENYOOM"
|
||||
],
|
||||
"acl_categories": [
|
||||
"ARRAY"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"RW",
|
||||
"UPDATE"
|
||||
],
|
||||
"begin_search": {
|
||||
"index": {
|
||||
"pos": 1
|
||||
}
|
||||
},
|
||||
"find_keys": {
|
||||
"range": {
|
||||
"lastkey": 0,
|
||||
"step": 1,
|
||||
"limit": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"description": "The last index where a value was inserted.",
|
||||
"type": "integer"
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key",
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "size",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
76
src/commands/arscan.json
Normal file
76
src/commands/arscan.json
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
{
|
||||
"ARSCAN": {
|
||||
"summary": "Iterates existing elements in a range, returning index-value pairs.",
|
||||
"complexity": "O(P) where P is visited positions in touched slices (dense scanned slots + sparse entries), with worst-case O(|end-start|+1) and typical case close to O(N), where N is the number of existing elements in range.",
|
||||
"group": "array",
|
||||
"since": "8.8.0",
|
||||
"arity": -4,
|
||||
"function": "arscanCommand",
|
||||
"command_flags": [
|
||||
"READONLY"
|
||||
],
|
||||
"acl_categories": [
|
||||
"ARRAY"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"RO",
|
||||
"ACCESS"
|
||||
],
|
||||
"begin_search": {
|
||||
"index": {
|
||||
"pos": 1
|
||||
}
|
||||
},
|
||||
"find_keys": {
|
||||
"range": {
|
||||
"lastkey": 0,
|
||||
"step": 1,
|
||||
"limit": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"description": "Array of [index, value] pairs.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "array",
|
||||
"minItems": 2,
|
||||
"maxItems": 2,
|
||||
"items": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Index of existing element"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Value at that index"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key",
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "start",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "end",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"token": "LIMIT",
|
||||
"type": "integer",
|
||||
"optional": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +1,22 @@
|
|||
{
|
||||
"GCRASETVALUE": {
|
||||
"summary": "An internal command for recording a GCRA TAT value during AOF rewrite and replication.",
|
||||
"ARSEEK": {
|
||||
"summary": "Sets the ARINSERT / ARRING cursor to a specific index.",
|
||||
"complexity": "O(1)",
|
||||
"group": "rate_limit",
|
||||
"group": "array",
|
||||
"since": "8.8.0",
|
||||
"arity": 3,
|
||||
"function": "gcraSetValueCommand",
|
||||
"function": "arseekCommand",
|
||||
"command_flags": [
|
||||
"WRITE",
|
||||
"DENYOOM",
|
||||
"FAST"
|
||||
],
|
||||
"acl_categories": [
|
||||
"RATE_LIMIT"
|
||||
"ARRAY"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"OW",
|
||||
"RW",
|
||||
"UPDATE"
|
||||
],
|
||||
"begin_search": {
|
||||
|
|
@ -35,7 +34,8 @@
|
|||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"const": "OK"
|
||||
"description": "1 if the cursor was set, 0 if the key does not exist.",
|
||||
"type": "integer"
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "tat",
|
||||
"name": "index",
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
58
src/commands/arset.json
Normal file
58
src/commands/arset.json
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"ARSET": {
|
||||
"summary": "Sets one or more contiguous values starting at an index in an array.",
|
||||
"complexity": "O(N) where N is the number of values",
|
||||
"group": "array",
|
||||
"since": "8.8.0",
|
||||
"arity": -4,
|
||||
"function": "arsetCommand",
|
||||
"command_flags": [
|
||||
"WRITE",
|
||||
"DENYOOM",
|
||||
"FAST"
|
||||
],
|
||||
"acl_categories": [
|
||||
"ARRAY"
|
||||
],
|
||||
"key_specs": [
|
||||
{
|
||||
"flags": [
|
||||
"RW",
|
||||
"UPDATE"
|
||||
],
|
||||
"begin_search": {
|
||||
"index": {
|
||||
"pos": 1
|
||||
}
|
||||
},
|
||||
"find_keys": {
|
||||
"range": {
|
||||
"lastkey": 0,
|
||||
"step": 1,
|
||||
"limit": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"reply_schema": {
|
||||
"description": "Number of new slots that were set (previously empty).",
|
||||
"type": "integer"
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "key",
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "index",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"type": "string",
|
||||
"multiple": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -59,6 +59,9 @@
|
|||
{
|
||||
"const": "hyperloglog"
|
||||
},
|
||||
{
|
||||
"const": "array"
|
||||
},
|
||||
{
|
||||
"const": "list"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,92 +0,0 @@
|
|||
{
|
||||
"GCRA": {
|
||||
"summary": "Rate limit via GCRA (Generic Cell Rate Algorithm).",
|
||||
"complexity": "O(1)",
|
||||
"group": "rate_limit",
|
||||
"since": "8.8.0",
|
||||
"arity": -5,
|
||||
"function": "gcraCommand",
|
||||
"command_flags": [
|
||||
"WRITE",
|
||||
"DENYOOM",
|
||||
"FAST"
|
||||
],
|
||||
"acl_categories": [
|
||||
"RATE_LIMIT"
|
||||
],
|
||||
"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 tokens: always equal to max_burst+1"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Number of tokens available immediately"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Retry after: seconds after which the 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": "tokens-per-period",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "period",
|
||||
"type": "double"
|
||||
},
|
||||
{
|
||||
"name": "count",
|
||||
"type": "integer",
|
||||
"token": "TOKENS",
|
||||
"optional": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,8 @@
|
|||
"NOSCRIPT",
|
||||
"LOADING",
|
||||
"STALE",
|
||||
"SENTINEL"
|
||||
"SENTINEL",
|
||||
"DENYOOM"
|
||||
],
|
||||
"arguments": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@
|
|||
"PUBSUB",
|
||||
"NOSCRIPT",
|
||||
"LOADING",
|
||||
"STALE"
|
||||
"STALE",
|
||||
"DENYOOM"
|
||||
],
|
||||
"arguments": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@
|
|||
"NOSCRIPT",
|
||||
"LOADING",
|
||||
"STALE",
|
||||
"SENTINEL"
|
||||
"SENTINEL",
|
||||
"DENYOOM"
|
||||
],
|
||||
"arguments": [
|
||||
{
|
||||
|
|
|
|||
81
src/config.c
81
src/config.c
|
|
@ -24,6 +24,7 @@
|
|||
#include <string.h>
|
||||
#include <locale.h>
|
||||
#include <ctype.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Config file name-value maps.
|
||||
|
|
@ -2428,13 +2429,7 @@ static int isValidAnnouncedNodename(char *val,const char **err) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
static int isValidAnnouncedHostname(char *val, const char **err) {
|
||||
if (strlen(val) >= NET_HOST_STR_LEN) {
|
||||
*err = "Hostnames must be less than "
|
||||
STRINGIFY(NET_HOST_STR_LEN) " characters";
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isValidHostnameChars(char *val, const char **err) {
|
||||
int i = 0;
|
||||
char c;
|
||||
while ((c = val[i])) {
|
||||
|
|
@ -2452,6 +2447,39 @@ static int isValidAnnouncedHostname(char *val, const char **err) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
static int isValidAnnouncedHostname(char *val, const char **err) {
|
||||
if (strlen(val) >= NET_HOST_STR_LEN) {
|
||||
*err = "Hostnames must be less than "
|
||||
STRINGIFY(NET_HOST_STR_LEN) " characters";
|
||||
return 0;
|
||||
}
|
||||
return isValidHostnameChars(val, err);
|
||||
}
|
||||
|
||||
/* Validation function for cluster-announce-ip.
|
||||
* Ensures the IP address is valid and rejects control characters. */
|
||||
static int isValidClusterAnnounceIp(char *val, const char **err) {
|
||||
unsigned char buf[sizeof(struct in6_addr)];
|
||||
/* Empty string is allowed - it will be converted to NULL by EMPTY_STRING_IS_NULL flag */
|
||||
if (val[0] == '\0') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Accept valid IPv4 or IPv6 */
|
||||
if (inet_pton(AF_INET, val, buf) == 1 || inet_pton(AF_INET6, val, buf) == 1) {
|
||||
return 1;
|
||||
}
|
||||
/* Also accept valid hostnames, but limited to NET_IP_STR_LEN since
|
||||
* cluster_announce_ip is stored in a NET_IP_STR_LEN buffer */
|
||||
if (strlen(val) >= NET_IP_STR_LEN) {
|
||||
*err = "Hostnames for cluster-announce-ip must be less than "
|
||||
STRINGIFY(NET_IP_STR_LEN) " characters";
|
||||
return 0;
|
||||
}
|
||||
/* Also accept valid hostnames */
|
||||
return isValidHostnameChars(val, err);
|
||||
}
|
||||
|
||||
/* Validate specified string is a valid proc-title-template */
|
||||
static int isValidProcTitleTemplate(char *val, const char **err) {
|
||||
if (!validateProcTitleTemplate(val)) {
|
||||
|
|
@ -2461,6 +2489,33 @@ static int isValidProcTitleTemplate(char *val, const char **err) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
/* Validate that array-slice-size is a power of two */
|
||||
static int isValidArraySliceSize(long long val, const char **err) {
|
||||
if (val <= 0 || (val & (val - 1)) != 0) {
|
||||
*err = "array-slice-size must be a power of two";
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Validate array-sparse-kmax: if non-zero, must be > kmin */
|
||||
static int isValidArraySparseKmax(long long val, const char **err) {
|
||||
if (val > 0 && (unsigned int)val <= server.array_sparse_kmin) {
|
||||
*err = "array-sparse-kmax must be greater than array-sparse-kmin when non-zero";
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Validate array-sparse-kmin: must be < kmax when kmax is non-zero */
|
||||
static int isValidArraySparseKmin(long long val, const char **err) {
|
||||
if (server.array_sparse_kmax > 0 && (unsigned int)val >= server.array_sparse_kmax) {
|
||||
*err = "array-sparse-kmin must be less than array-sparse-kmax";
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int updateLocaleCollate(const char **err) {
|
||||
const char *s = setlocale(LC_COLLATE, server.locale_collate);
|
||||
if (s == NULL) {
|
||||
|
|
@ -2917,7 +2972,11 @@ static int setConfigNotifyKeyspaceEventsOption(standardConfig *config, sds *argv
|
|||
}
|
||||
int flags = keyspaceEventsStringToFlags(argv[0]);
|
||||
if (flags == -1) {
|
||||
*err = "Invalid event class character. Use 'Ag$lshzxeKEtmdnocrSTIV'.";
|
||||
#ifdef ENABLE_GCRA
|
||||
*err = "Invalid event class character. Use 'Ag$lshzxeKEtmdnocraSTIV'.";
|
||||
#else
|
||||
*err = "Invalid event class character. Use 'Ag$lshzxeKEtmdnocaSTIV'.";
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
server.notify_keyspace_events = flags;
|
||||
|
|
@ -3159,7 +3218,7 @@ standardConfig static_configs[] = {
|
|||
createStringConfig("pidfile", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.pidfile, NULL, NULL, NULL),
|
||||
createStringConfig("replica-announce-ip", "slave-announce-ip", MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.slave_announce_ip, NULL, NULL, NULL),
|
||||
createStringConfig("masteruser", NULL, MODIFIABLE_CONFIG | SENSITIVE_CONFIG, EMPTY_STRING_IS_NULL, server.masteruser, NULL, NULL, NULL),
|
||||
createStringConfig("cluster-announce-ip", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.cluster_announce_ip, NULL, NULL, updateClusterIp),
|
||||
createStringConfig("cluster-announce-ip", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.cluster_announce_ip, NULL, isValidClusterAnnounceIp, updateClusterIp),
|
||||
createStringConfig("cluster-config-file", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.cluster_configfile, "nodes.conf", NULL, NULL),
|
||||
createStringConfig("cluster-announce-hostname", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.cluster_announce_hostname, NULL, isValidAnnouncedHostname, updateClusterHostname),
|
||||
createStringConfig("cluster-announce-human-nodename", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.cluster_announce_human_nodename, NULL, isValidAnnouncedNodename, updateClusterHumanNodename),
|
||||
|
|
@ -3252,6 +3311,10 @@ standardConfig static_configs[] = {
|
|||
createUIntConfig("socket-mark-id", NULL, IMMUTABLE_CONFIG, 0, UINT_MAX, server.socket_mark_id, 0, INTEGER_CONFIG, NULL, NULL),
|
||||
createUIntConfig("max-new-connections-per-cycle", NULL, MODIFIABLE_CONFIG, 1, 1000, server.max_new_conns_per_cycle, 10, INTEGER_CONFIG, NULL, NULL),
|
||||
createUIntConfig("max-new-tls-connections-per-cycle", NULL, MODIFIABLE_CONFIG, 1, 1000, server.max_new_tls_conns_per_cycle, 1, INTEGER_CONFIG, NULL, NULL),
|
||||
/* Array type configuration */
|
||||
createUIntConfig("array-slice-size", NULL, MODIFIABLE_CONFIG, AR_SLICE_SIZE_MIN, AR_SLICE_SIZE_MAX, server.array_slice_size, AR_SLICE_SIZE_DEFAULT, INTEGER_CONFIG, isValidArraySliceSize, NULL),
|
||||
createUIntConfig("array-sparse-kmax", NULL, MODIFIABLE_CONFIG, 0, 256, server.array_sparse_kmax, AR_SPARSE_KMAX_DEFAULT, INTEGER_CONFIG, isValidArraySparseKmax, NULL),
|
||||
createUIntConfig("array-sparse-kmin", NULL, MODIFIABLE_CONFIG, 0, 256, server.array_sparse_kmin, AR_SPARSE_KMIN_DEFAULT, INTEGER_CONFIG, isValidArraySparseKmin, NULL),
|
||||
#ifdef LOG_REQ_RES
|
||||
createUIntConfig("client-default-resp", NULL, IMMUTABLE_CONFIG | HIDDEN_CONFIG, 2, 3, server.client_default_resp, 2, INTEGER_CONFIG, NULL, NULL),
|
||||
#endif
|
||||
|
|
|
|||
16
src/db.c
16
src/db.c
|
|
@ -1751,14 +1751,17 @@ int parseScanCursorOrReply(client *c, robj *o, unsigned long long *cursor) {
|
|||
}
|
||||
|
||||
char *obj_type_name[OBJ_TYPE_MAX] = {
|
||||
"string",
|
||||
"list",
|
||||
"set",
|
||||
"zset",
|
||||
"hash",
|
||||
"string",
|
||||
"list",
|
||||
"set",
|
||||
"zset",
|
||||
"hash",
|
||||
NULL, /* module type is special */
|
||||
"stream",
|
||||
"array",
|
||||
#ifdef ENABLE_GCRA
|
||||
"gcra"
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Helper function to get type from a string in scan commands */
|
||||
|
|
@ -2433,11 +2436,14 @@ void copyCommand(client *c) {
|
|||
case OBJ_ZSET: newobj = zsetDup(o); break;
|
||||
case OBJ_HASH: newobj = hashTypeDup(o, &minHashExpire); break;
|
||||
case OBJ_STREAM: newobj = streamDup(o); break;
|
||||
#ifdef ENABLE_GCRA
|
||||
case OBJ_GCRA: newobj = gcraDup(o); break;
|
||||
#endif
|
||||
case OBJ_MODULE:
|
||||
newobj = moduleTypeDupOrReply(c, key, newkey, dst->id, o);
|
||||
if (!newobj) return;
|
||||
break;
|
||||
case OBJ_ARRAY: newobj = arrayTypeDup(o); break;
|
||||
default:
|
||||
addReplyError(c, "unknown type object");
|
||||
return;
|
||||
|
|
|
|||
21
src/debug.c
21
src/debug.c
|
|
@ -123,6 +123,7 @@ void mixStringObjectDigest(unsigned char *digest, robj *o) {
|
|||
decrRefCount(o);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_GCRA
|
||||
void mixGCRAObjectDigest(unsigned char *digest, robj *o) {
|
||||
char buf[LONG_STR_SIZE];
|
||||
long long val;
|
||||
|
|
@ -130,6 +131,7 @@ void mixGCRAObjectDigest(unsigned char *digest, robj *o) {
|
|||
int len = ll2string(buf, sizeof(buf), val);
|
||||
mixDigest(digest,buf,len);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* This function computes the digest of a data structure stored in the
|
||||
* object 'o'. It is the core of the DEBUG DIGEST command: when taking the
|
||||
|
|
@ -263,8 +265,10 @@ void xorObjectDigest(redisDb *db, robj *keyobj, unsigned char *digest, robj *o)
|
|||
}
|
||||
}
|
||||
streamIteratorStop(&si);
|
||||
#ifdef ENABLE_GCRA
|
||||
} else if (o->type == OBJ_GCRA) {
|
||||
mixGCRAObjectDigest(digest, o);
|
||||
#endif
|
||||
} else if (o->type == OBJ_MODULE) {
|
||||
RedisModuleDigest md = {{0},{0},keyobj,db->id};
|
||||
moduleValue *mv = o->ptr;
|
||||
|
|
@ -274,6 +278,21 @@ void xorObjectDigest(redisDb *db, robj *keyobj, unsigned char *digest, robj *o)
|
|||
mt->digest(&md,mv->value);
|
||||
xorDigest(digest,md.x,sizeof(md.x));
|
||||
}
|
||||
} else if (o->type == OBJ_ARRAY) {
|
||||
redisArray *ar = o->ptr;
|
||||
uint64_t len = arLen(ar);
|
||||
for (uint64_t idx = 0; idx < len; idx++) {
|
||||
void *v = arGet(ar, idx);
|
||||
if (arIsEmpty(v)) {
|
||||
/* For empty slots, contribute "(null)" */
|
||||
mixDigest(digest, "(null)", 6);
|
||||
} else {
|
||||
char vbuf[AR_INLINE_BUFSIZE];
|
||||
size_t vlen;
|
||||
const char *data = arDecode(v, vbuf, sizeof(vbuf), &vlen);
|
||||
mixDigest(digest, data, vlen);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
serverPanic("Unknown object type");
|
||||
}
|
||||
|
|
@ -1312,9 +1331,11 @@ void serverLogObjectDebugInfo(const robj *o) {
|
|||
serverLog(LL_WARNING,"Skiplist level: %d", (int) ((const zset*)o->ptr)->zsl->level);
|
||||
} else if (o->type == OBJ_STREAM) {
|
||||
serverLog(LL_WARNING,"Stream size: %d", (int) streamLength(o));
|
||||
#ifdef ENABLE_GCRA
|
||||
} else if (o->type == OBJ_GCRA) {
|
||||
#if UINTPTR_MAX == 0xffffffffffffffff
|
||||
serverLog(LL_WARNING, "GCRA object: %lld", (long long)o->ptr);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
34
src/defrag.c
34
src/defrag.c
|
|
@ -754,6 +754,32 @@ void defragSet(defragKeysCtx *ctx, kvobj *ob) {
|
|||
ob->ptr = newd;
|
||||
}
|
||||
|
||||
/* Arrays can be expensive to defrag in one shot because they may contain many
|
||||
* independently allocated slices. Small arrays are defragmented immediately,
|
||||
* while large arrays are queued for later and processed one slice per step. */
|
||||
void defragArray(defragKeysCtx *ctx, kvobj *ob) {
|
||||
serverAssert(ob->type == OBJ_ARRAY);
|
||||
/* Maybe arCount() is not the best possible value to check against
|
||||
* server.active_defrag_max_scan_fields, also because anyway when we
|
||||
* defrag incrementally, we defrag a since slice per call. Yet it makes
|
||||
* sense in a non very obvious way, for several reasons:
|
||||
*
|
||||
* 1. If the array is very sparse, it is an upper bound to the max
|
||||
* number of slices it is composed to.
|
||||
* 2. If the array is dense, we will scan in the default case at most 4096
|
||||
* entries, and the default defrag limit for max scans is 1000. They
|
||||
* are kinda comparable numbers.
|
||||
* 3. In case of a highly sparse array with huge indexes, in superdir mode,
|
||||
* yet the super blocks are going to be at max arCount().
|
||||
*
|
||||
* So regardless of the fact we later will defrag in slice units, this
|
||||
* is a good trigger for the one shot or incremental selection. */
|
||||
if (arCount(ob->ptr) > server.active_defrag_max_scan_fields)
|
||||
defragLater(ctx, ob);
|
||||
else
|
||||
ob->ptr = arDefrag(ob->ptr, activeDefragAlloc);
|
||||
}
|
||||
|
||||
/* Defrag callback for radix tree iterator, called for each node,
|
||||
* used in order to defrag the nodes allocations. */
|
||||
int defragRaxNode(raxNode **noderef, void *privdata) {
|
||||
|
|
@ -1163,15 +1189,19 @@ void defragKey(defragKeysCtx *ctx, dictEntry *de, dictEntryLink link) {
|
|||
}
|
||||
} else if (ob->type == OBJ_STREAM) {
|
||||
defragStream(ctx, ob);
|
||||
#ifdef ENABLE_GCRA
|
||||
} else if (ob->type == OBJ_GCRA) {
|
||||
/* GCRA object is just an allocation to a long long value */
|
||||
#if UINTPTR_MAX == 0xffffffff
|
||||
void *newptr, *ptr = ob->ptr;
|
||||
if ((newptr = activeDefragAlloc(ptr)))
|
||||
ob->ptr = newptr;
|
||||
#endif
|
||||
#endif
|
||||
} else if (ob->type == OBJ_MODULE) {
|
||||
defragModule(ctx,db, ob);
|
||||
} else if (ob->type == OBJ_ARRAY) {
|
||||
defragArray(ctx, ob);
|
||||
} else {
|
||||
serverPanic("Unknown object type");
|
||||
}
|
||||
|
|
@ -1288,6 +1318,10 @@ int defragLaterItem(kvobj *ob, unsigned long *cursor, monotime endtime, int dbid
|
|||
robj keyobj;
|
||||
initStaticStringObject(keyobj, kvobjGetKey(ob));
|
||||
return moduleLateDefrag(&keyobj, ob, cursor, endtime, dbid);
|
||||
} else if (ob->type == OBJ_ARRAY) {
|
||||
redisArray *ar = ob->ptr;
|
||||
*cursor = arDefragIncremental(&ar, *cursor, activeDefragAlloc);
|
||||
ob->ptr = ar;
|
||||
} else {
|
||||
*cursor = 0; /* object type/encoding may have changed since we schedule it for later */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1502,7 +1502,9 @@ void ldbEval(lua_State *lua, sds *argv, int argc) {
|
|||
sdsfree(code);
|
||||
sdsfree(expr);
|
||||
if (lua_pcall(lua,0,1,0)) {
|
||||
ldbLog(sdscatfmt(sdsempty(),"<error> %s",lua_tostring(lua,-1)));
|
||||
const char *err = lua_tostring(lua,-1);
|
||||
ldbLog(sdscatfmt(sdsempty(),"<error> %s",
|
||||
err ? err : "(error object is not a string)"));
|
||||
lua_pop(lua,1);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,195 @@ static const double powers_of_ten[] = {
|
|||
1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22
|
||||
};
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Eisel-Lemire algorithm — extended-precision powers of five.
|
||||
*
|
||||
* The table below maps from decimal scaling (10^q) to a 128-bit binary
|
||||
* approximation. Since 10^q = 2^q * 5^q and the 2^q factor is exact in
|
||||
* binary, only 5^q affects the binary significand — so we precompute
|
||||
* 5^q rounded toward 1 to 128 bits. Used by `compute_float()` to avoid
|
||||
* any iterative rounding in the widened (mantissa > 2^53) range.
|
||||
*
|
||||
* Pulled verbatim from fast_float by Daniel Lemire & Joao Paulo Magalhaes
|
||||
* (MIT-licensed, https://github.com/fastfloat/fast_float — fast_table.h).
|
||||
*
|
||||
* Range: 5^-342 ... 5^308 — covers every value that can produce a finite
|
||||
* non-zero double from a 64-bit decimal mantissa. 651 entries, each stored
|
||||
* as { high64, low64 } pairs (1302 uint64_t total).
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
#define EISEL_LEMIRE_SMALLEST_POWER_OF_FIVE -342
|
||||
#define EISEL_LEMIRE_LARGEST_POWER_OF_FIVE 308
|
||||
#define EISEL_LEMIRE_NUMBER_OF_ENTRIES (2 * (EISEL_LEMIRE_LARGEST_POWER_OF_FIVE - \
|
||||
EISEL_LEMIRE_SMALLEST_POWER_OF_FIVE + 1))
|
||||
|
||||
static const uint64_t power_of_five_128[EISEL_LEMIRE_NUMBER_OF_ENTRIES] = {
|
||||
0xeef453d6923bd65a, 0x113faa2906a13b3f, 0x9558b4661b6565f8, 0x4ac7ca59a424c507, 0xbaaee17fa23ebf76, 0x5d79bcf00d2df649, 0xe95a99df8ace6f53, 0xf4d82c2c107973dc,
|
||||
0x91d8a02bb6c10594, 0x79071b9b8a4be869, 0xb64ec836a47146f9, 0x9748e2826cdee284, 0xe3e27a444d8d98b7, 0xfd1b1b2308169b25, 0x8e6d8c6ab0787f72, 0xfe30f0f5e50e20f7,
|
||||
0xb208ef855c969f4f, 0xbdbd2d335e51a935, 0xde8b2b66b3bc4723, 0xad2c788035e61382, 0x8b16fb203055ac76, 0x4c3bcb5021afcc31, 0xaddcb9e83c6b1793, 0xdf4abe242a1bbf3d,
|
||||
0xd953e8624b85dd78, 0xd71d6dad34a2af0d, 0x87d4713d6f33aa6b, 0x8672648c40e5ad68, 0xa9c98d8ccb009506, 0x680efdaf511f18c2, 0xd43bf0effdc0ba48, 0x212bd1b2566def2,
|
||||
0x84a57695fe98746d, 0x14bb630f7604b57, 0xa5ced43b7e3e9188, 0x419ea3bd35385e2d, 0xcf42894a5dce35ea, 0x52064cac828675b9, 0x818995ce7aa0e1b2, 0x7343efebd1940993,
|
||||
0xa1ebfb4219491a1f, 0x1014ebe6c5f90bf8, 0xca66fa129f9b60a6, 0xd41a26e077774ef6, 0xfd00b897478238d0, 0x8920b098955522b4, 0x9e20735e8cb16382, 0x55b46e5f5d5535b0,
|
||||
0xc5a890362fddbc62, 0xeb2189f734aa831d, 0xf712b443bbd52b7b, 0xa5e9ec7501d523e4, 0x9a6bb0aa55653b2d, 0x47b233c92125366e, 0xc1069cd4eabe89f8, 0x999ec0bb696e840a,
|
||||
0xf148440a256e2c76, 0xc00670ea43ca250d, 0x96cd2a865764dbca, 0x380406926a5e5728, 0xbc807527ed3e12bc, 0xc605083704f5ecf2, 0xeba09271e88d976b, 0xf7864a44c633682e,
|
||||
0x93445b8731587ea3, 0x7ab3ee6afbe0211d, 0xb8157268fdae9e4c, 0x5960ea05bad82964, 0xe61acf033d1a45df, 0x6fb92487298e33bd, 0x8fd0c16206306bab, 0xa5d3b6d479f8e056,
|
||||
0xb3c4f1ba87bc8696, 0x8f48a4899877186c, 0xe0b62e2929aba83c, 0x331acdabfe94de87, 0x8c71dcd9ba0b4925, 0x9ff0c08b7f1d0b14, 0xaf8e5410288e1b6f, 0x7ecf0ae5ee44dd9,
|
||||
0xdb71e91432b1a24a, 0xc9e82cd9f69d6150, 0x892731ac9faf056e, 0xbe311c083a225cd2, 0xab70fe17c79ac6ca, 0x6dbd630a48aaf406, 0xd64d3d9db981787d, 0x92cbbccdad5b108,
|
||||
0x85f0468293f0eb4e, 0x25bbf56008c58ea5, 0xa76c582338ed2621, 0xaf2af2b80af6f24e, 0xd1476e2c07286faa, 0x1af5af660db4aee1, 0x82cca4db847945ca, 0x50d98d9fc890ed4d,
|
||||
0xa37fce126597973c, 0xe50ff107bab528a0, 0xcc5fc196fefd7d0c, 0x1e53ed49a96272c8, 0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7a, 0x9faacf3df73609b1, 0x77b191618c54e9ac,
|
||||
0xc795830d75038c1d, 0xd59df5b9ef6a2417, 0xf97ae3d0d2446f25, 0x4b0573286b44ad1d, 0x9becce62836ac577, 0x4ee367f9430aec32, 0xc2e801fb244576d5, 0x229c41f793cda73f,
|
||||
0xf3a20279ed56d48a, 0x6b43527578c1110f, 0x9845418c345644d6, 0x830a13896b78aaa9, 0xbe5691ef416bd60c, 0x23cc986bc656d553, 0xedec366b11c6cb8f, 0x2cbfbe86b7ec8aa8,
|
||||
0x94b3a202eb1c3f39, 0x7bf7d71432f3d6a9, 0xb9e08a83a5e34f07, 0xdaf5ccd93fb0cc53, 0xe858ad248f5c22c9, 0xd1b3400f8f9cff68, 0x91376c36d99995be, 0x23100809b9c21fa1,
|
||||
0xb58547448ffffb2d, 0xabd40a0c2832a78a, 0xe2e69915b3fff9f9, 0x16c90c8f323f516c, 0x8dd01fad907ffc3b, 0xae3da7d97f6792e3, 0xb1442798f49ffb4a, 0x99cd11cfdf41779c,
|
||||
0xdd95317f31c7fa1d, 0x40405643d711d583, 0x8a7d3eef7f1cfc52, 0x482835ea666b2572, 0xad1c8eab5ee43b66, 0xda3243650005eecf, 0xd863b256369d4a40, 0x90bed43e40076a82,
|
||||
0x873e4f75e2224e68, 0x5a7744a6e804a291, 0xa90de3535aaae202, 0x711515d0a205cb36, 0xd3515c2831559a83, 0xd5a5b44ca873e03, 0x8412d9991ed58091, 0xe858790afe9486c2,
|
||||
0xa5178fff668ae0b6, 0x626e974dbe39a872, 0xce5d73ff402d98e3, 0xfb0a3d212dc8128f, 0x80fa687f881c7f8e, 0x7ce66634bc9d0b99, 0xa139029f6a239f72, 0x1c1fffc1ebc44e80,
|
||||
0xc987434744ac874e, 0xa327ffb266b56220, 0xfbe9141915d7a922, 0x4bf1ff9f0062baa8, 0x9d71ac8fada6c9b5, 0x6f773fc3603db4a9, 0xc4ce17b399107c22, 0xcb550fb4384d21d3,
|
||||
0xf6019da07f549b2b, 0x7e2a53a146606a48, 0x99c102844f94e0fb, 0x2eda7444cbfc426d, 0xc0314325637a1939, 0xfa911155fefb5308, 0xf03d93eebc589f88, 0x793555ab7eba27ca,
|
||||
0x96267c7535b763b5, 0x4bc1558b2f3458de, 0xbbb01b9283253ca2, 0x9eb1aaedfb016f16, 0xea9c227723ee8bcb, 0x465e15a979c1cadc, 0x92a1958a7675175f, 0xbfacd89ec191ec9,
|
||||
0xb749faed14125d36, 0xcef980ec671f667b, 0xe51c79a85916f484, 0x82b7e12780e7401a, 0x8f31cc0937ae58d2, 0xd1b2ecb8b0908810, 0xb2fe3f0b8599ef07, 0x861fa7e6dcb4aa15,
|
||||
0xdfbdcece67006ac9, 0x67a791e093e1d49a, 0x8bd6a141006042bd, 0xe0c8bb2c5c6d24e0, 0xaecc49914078536d, 0x58fae9f773886e18, 0xda7f5bf590966848, 0xaf39a475506a899e,
|
||||
0x888f99797a5e012d, 0x6d8406c952429603, 0xaab37fd7d8f58178, 0xc8e5087ba6d33b83, 0xd5605fcdcf32e1d6, 0xfb1e4a9a90880a64, 0x855c3be0a17fcd26, 0x5cf2eea09a55067f,
|
||||
0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481e, 0xd0601d8efc57b08b, 0xf13b94daf124da26, 0x823c12795db6ce57, 0x76c53d08d6b70858, 0xa2cb1717b52481ed, 0x54768c4b0c64ca6e,
|
||||
0xcb7ddcdda26da268, 0xa9942f5dcf7dfd09, 0xfe5d54150b090b02, 0xd3f93b35435d7c4c, 0x9efa548d26e5a6e1, 0xc47bc5014a1a6daf, 0xc6b8e9b0709f109a, 0x359ab6419ca1091b,
|
||||
0xf867241c8cc6d4c0, 0xc30163d203c94b62, 0x9b407691d7fc44f8, 0x79e0de63425dcf1d, 0xc21094364dfb5636, 0x985915fc12f542e4, 0xf294b943e17a2bc4, 0x3e6f5b7b17b2939d,
|
||||
0x979cf3ca6cec5b5a, 0xa705992ceecf9c42, 0xbd8430bd08277231, 0x50c6ff782a838353, 0xece53cec4a314ebd, 0xa4f8bf5635246428, 0x940f4613ae5ed136, 0x871b7795e136be99,
|
||||
0xb913179899f68584, 0x28e2557b59846e3f, 0xe757dd7ec07426e5, 0x331aeada2fe589cf, 0x9096ea6f3848984f, 0x3ff0d2c85def7621, 0xb4bca50b065abe63, 0xfed077a756b53a9,
|
||||
0xe1ebce4dc7f16dfb, 0xd3e8495912c62894, 0x8d3360f09cf6e4bd, 0x64712dd7abbbd95c, 0xb080392cc4349dec, 0xbd8d794d96aacfb3, 0xdca04777f541c567, 0xecf0d7a0fc5583a0,
|
||||
0x89e42caaf9491b60, 0xf41686c49db57244, 0xac5d37d5b79b6239, 0x311c2875c522ced5, 0xd77485cb25823ac7, 0x7d633293366b828b, 0x86a8d39ef77164bc, 0xae5dff9c02033197,
|
||||
0xa8530886b54dbdeb, 0xd9f57f830283fdfc, 0xd267caa862a12d66, 0xd072df63c324fd7b, 0x8380dea93da4bc60, 0x4247cb9e59f71e6d, 0xa46116538d0deb78, 0x52d9be85f074e608,
|
||||
0xcd795be870516656, 0x67902e276c921f8b, 0x806bd9714632dff6, 0xba1cd8a3db53b6, 0xa086cfcd97bf97f3, 0x80e8a40eccd228a4, 0xc8a883c0fdaf7df0, 0x6122cd128006b2cd,
|
||||
0xfad2a4b13d1b5d6c, 0x796b805720085f81, 0x9cc3a6eec6311a63, 0xcbe3303674053bb0, 0xc3f490aa77bd60fc, 0xbedbfc4411068a9c, 0xf4f1b4d515acb93b, 0xee92fb5515482d44,
|
||||
0x991711052d8bf3c5, 0x751bdd152d4d1c4a, 0xbf5cd54678eef0b6, 0xd262d45a78a0635d, 0xef340a98172aace4, 0x86fb897116c87c34, 0x9580869f0e7aac0e, 0xd45d35e6ae3d4da0,
|
||||
0xbae0a846d2195712, 0x8974836059cca109, 0xe998d258869facd7, 0x2bd1a438703fc94b, 0x91ff83775423cc06, 0x7b6306a34627ddcf, 0xb67f6455292cbf08, 0x1a3bc84c17b1d542,
|
||||
0xe41f3d6a7377eeca, 0x20caba5f1d9e4a93, 0x8e938662882af53e, 0x547eb47b7282ee9c, 0xb23867fb2a35b28d, 0xe99e619a4f23aa43, 0xdec681f9f4c31f31, 0x6405fa00e2ec94d4,
|
||||
0x8b3c113c38f9f37e, 0xde83bc408dd3dd04, 0xae0b158b4738705e, 0x9624ab50b148d445, 0xd98ddaee19068c76, 0x3badd624dd9b0957, 0x87f8a8d4cfa417c9, 0xe54ca5d70a80e5d6,
|
||||
0xa9f6d30a038d1dbc, 0x5e9fcf4ccd211f4c, 0xd47487cc8470652b, 0x7647c3200069671f, 0x84c8d4dfd2c63f3b, 0x29ecd9f40041e073, 0xa5fb0a17c777cf09, 0xf468107100525890,
|
||||
0xcf79cc9db955c2cc, 0x7182148d4066eeb4, 0x81ac1fe293d599bf, 0xc6f14cd848405530, 0xa21727db38cb002f, 0xb8ada00e5a506a7c, 0xca9cf1d206fdc03b, 0xa6d90811f0e4851c,
|
||||
0xfd442e4688bd304a, 0x908f4a166d1da663, 0x9e4a9cec15763e2e, 0x9a598e4e043287fe, 0xc5dd44271ad3cdba, 0x40eff1e1853f29fd, 0xf7549530e188c128, 0xd12bee59e68ef47c,
|
||||
0x9a94dd3e8cf578b9, 0x82bb74f8301958ce, 0xc13a148e3032d6e7, 0xe36a52363c1faf01, 0xf18899b1bc3f8ca1, 0xdc44e6c3cb279ac1, 0x96f5600f15a7b7e5, 0x29ab103a5ef8c0b9,
|
||||
0xbcb2b812db11a5de, 0x7415d448f6b6f0e7, 0xebdf661791d60f56, 0x111b495b3464ad21, 0x936b9fcebb25c995, 0xcab10dd900beec34, 0xb84687c269ef3bfb, 0x3d5d514f40eea742,
|
||||
0xe65829b3046b0afa, 0xcb4a5a3112a5112, 0x8ff71a0fe2c2e6dc, 0x47f0e785eaba72ab, 0xb3f4e093db73a093, 0x59ed216765690f56, 0xe0f218b8d25088b8, 0x306869c13ec3532c,
|
||||
0x8c974f7383725573, 0x1e414218c73a13fb, 0xafbd2350644eeacf, 0xe5d1929ef90898fa, 0xdbac6c247d62a583, 0xdf45f746b74abf39, 0x894bc396ce5da772, 0x6b8bba8c328eb783,
|
||||
0xab9eb47c81f5114f, 0x66ea92f3f326564, 0xd686619ba27255a2, 0xc80a537b0efefebd, 0x8613fd0145877585, 0xbd06742ce95f5f36, 0xa798fc4196e952e7, 0x2c48113823b73704,
|
||||
0xd17f3b51fca3a7a0, 0xf75a15862ca504c5, 0x82ef85133de648c4, 0x9a984d73dbe722fb, 0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebba, 0xcc963fee10b7d1b3, 0x318df905079926a8,
|
||||
0xffbbcfe994e5c61f, 0xfdf17746497f7052, 0x9fd561f1fd0f9bd3, 0xfeb6ea8bedefa633, 0xc7caba6e7c5382c8, 0xfe64a52ee96b8fc0, 0xf9bd690a1b68637b, 0x3dfdce7aa3c673b0,
|
||||
0x9c1661a651213e2d, 0x6bea10ca65c084e, 0xc31bfa0fe5698db8, 0x486e494fcff30a62, 0xf3e2f893dec3f126, 0x5a89dba3c3efccfa, 0x986ddb5c6b3a76b7, 0xf89629465a75e01c,
|
||||
0xbe89523386091465, 0xf6bbb397f1135823, 0xee2ba6c0678b597f, 0x746aa07ded582e2c, 0x94db483840b717ef, 0xa8c2a44eb4571cdc, 0xba121a4650e4ddeb, 0x92f34d62616ce413,
|
||||
0xe896a0d7e51e1566, 0x77b020baf9c81d17, 0x915e2486ef32cd60, 0xace1474dc1d122e, 0xb5b5ada8aaff80b8, 0xd819992132456ba, 0xe3231912d5bf60e6, 0x10e1fff697ed6c69,
|
||||
0x8df5efabc5979c8f, 0xca8d3ffa1ef463c1, 0xb1736b96b6fd83b3, 0xbd308ff8a6b17cb2, 0xddd0467c64bce4a0, 0xac7cb3f6d05ddbde, 0x8aa22c0dbef60ee4, 0x6bcdf07a423aa96b,
|
||||
0xad4ab7112eb3929d, 0x86c16c98d2c953c6, 0xd89d64d57a607744, 0xe871c7bf077ba8b7, 0x87625f056c7c4a8b, 0x11471cd764ad4972, 0xa93af6c6c79b5d2d, 0xd598e40d3dd89bcf,
|
||||
0xd389b47879823479, 0x4aff1d108d4ec2c3, 0x843610cb4bf160cb, 0xcedf722a585139ba, 0xa54394fe1eedb8fe, 0xc2974eb4ee658828, 0xce947a3da6a9273e, 0x733d226229feea32,
|
||||
0x811ccc668829b887, 0x806357d5a3f525f, 0xa163ff802a3426a8, 0xca07c2dcb0cf26f7, 0xc9bcff6034c13052, 0xfc89b393dd02f0b5, 0xfc2c3f3841f17c67, 0xbbac2078d443ace2,
|
||||
0x9d9ba7832936edc0, 0xd54b944b84aa4c0d, 0xc5029163f384a931, 0xa9e795e65d4df11, 0xf64335bcf065d37d, 0x4d4617b5ff4a16d5, 0x99ea0196163fa42e, 0x504bced1bf8e4e45,
|
||||
0xc06481fb9bcf8d39, 0xe45ec2862f71e1d6, 0xf07da27a82c37088, 0x5d767327bb4e5a4c, 0x964e858c91ba2655, 0x3a6a07f8d510f86f, 0xbbe226efb628afea, 0x890489f70a55368b,
|
||||
0xeadab0aba3b2dbe5, 0x2b45ac74ccea842e, 0x92c8ae6b464fc96f, 0x3b0b8bc90012929d, 0xb77ada0617e3bbcb, 0x9ce6ebb40173744, 0xe55990879ddcaabd, 0xcc420a6a101d0515,
|
||||
0x8f57fa54c2a9eab6, 0x9fa946824a12232d, 0xb32df8e9f3546564, 0x47939822dc96abf9, 0xdff9772470297ebd, 0x59787e2b93bc56f7, 0x8bfbea76c619ef36, 0x57eb4edb3c55b65a,
|
||||
0xaefae51477a06b03, 0xede622920b6b23f1, 0xdab99e59958885c4, 0xe95fab368e45eced, 0x88b402f7fd75539b, 0x11dbcb0218ebb414, 0xaae103b5fcd2a881, 0xd652bdc29f26a119,
|
||||
0xd59944a37c0752a2, 0x4be76d3346f0495f, 0x857fcae62d8493a5, 0x6f70a4400c562ddb, 0xa6dfbd9fb8e5b88e, 0xcb4ccd500f6bb952, 0xd097ad07a71f26b2, 0x7e2000a41346a7a7,
|
||||
0x825ecc24c873782f, 0x8ed400668c0c28c8, 0xa2f67f2dfa90563b, 0x728900802f0f32fa, 0xcbb41ef979346bca, 0x4f2b40a03ad2ffb9, 0xfea126b7d78186bc, 0xe2f610c84987bfa8,
|
||||
0x9f24b832e6b0f436, 0xdd9ca7d2df4d7c9, 0xc6ede63fa05d3143, 0x91503d1c79720dbb, 0xf8a95fcf88747d94, 0x75a44c6397ce912a, 0x9b69dbe1b548ce7c, 0xc986afbe3ee11aba,
|
||||
0xc24452da229b021b, 0xfbe85badce996168, 0xf2d56790ab41c2a2, 0xfae27299423fb9c3, 0x97c560ba6b0919a5, 0xdccd879fc967d41a, 0xbdb6b8e905cb600f, 0x5400e987bbc1c920,
|
||||
0xed246723473e3813, 0x290123e9aab23b68, 0x9436c0760c86e30b, 0xf9a0b6720aaf6521, 0xb94470938fa89bce, 0xf808e40e8d5b3e69, 0xe7958cb87392c2c2, 0xb60b1d1230b20e04,
|
||||
0x90bd77f3483bb9b9, 0xb1c6f22b5e6f48c2, 0xb4ecd5f01a4aa828, 0x1e38aeb6360b1af3, 0xe2280b6c20dd5232, 0x25c6da63c38de1b0, 0x8d590723948a535f, 0x579c487e5a38ad0e,
|
||||
0xb0af48ec79ace837, 0x2d835a9df0c6d851, 0xdcdb1b2798182244, 0xf8e431456cf88e65, 0x8a08f0f8bf0f156b, 0x1b8e9ecb641b58ff, 0xac8b2d36eed2dac5, 0xe272467e3d222f3f,
|
||||
0xd7adf884aa879177, 0x5b0ed81dcc6abb0f, 0x86ccbb52ea94baea, 0x98e947129fc2b4e9, 0xa87fea27a539e9a5, 0x3f2398d747b36224, 0xd29fe4b18e88640e, 0x8eec7f0d19a03aad,
|
||||
0x83a3eeeef9153e89, 0x1953cf68300424ac, 0xa48ceaaab75a8e2b, 0x5fa8c3423c052dd7, 0xcdb02555653131b6, 0x3792f412cb06794d, 0x808e17555f3ebf11, 0xe2bbd88bbee40bd0,
|
||||
0xa0b19d2ab70e6ed6, 0x5b6aceaeae9d0ec4, 0xc8de047564d20a8b, 0xf245825a5a445275, 0xfb158592be068d2e, 0xeed6e2f0f0d56712, 0x9ced737bb6c4183d, 0x55464dd69685606b,
|
||||
0xc428d05aa4751e4c, 0xaa97e14c3c26b886, 0xf53304714d9265df, 0xd53dd99f4b3066a8, 0x993fe2c6d07b7fab, 0xe546a8038efe4029, 0xbf8fdb78849a5f96, 0xde98520472bdd033,
|
||||
0xef73d256a5c0f77c, 0x963e66858f6d4440, 0x95a8637627989aad, 0xdde7001379a44aa8, 0xbb127c53b17ec159, 0x5560c018580d5d52, 0xe9d71b689dde71af, 0xaab8f01e6e10b4a6,
|
||||
0x9226712162ab070d, 0xcab3961304ca70e8, 0xb6b00d69bb55c8d1, 0x3d607b97c5fd0d22, 0xe45c10c42a2b3b05, 0x8cb89a7db77c506a, 0x8eb98a7a9a5b04e3, 0x77f3608e92adb242,
|
||||
0xb267ed1940f1c61c, 0x55f038b237591ed3, 0xdf01e85f912e37a3, 0x6b6c46dec52f6688, 0x8b61313bbabce2c6, 0x2323ac4b3b3da015, 0xae397d8aa96c1b77, 0xabec975e0a0d081a,
|
||||
0xd9c7dced53c72255, 0x96e7bd358c904a21, 0x881cea14545c7575, 0x7e50d64177da2e54, 0xaa242499697392d2, 0xdde50bd1d5d0b9e9, 0xd4ad2dbfc3d07787, 0x955e4ec64b44e864,
|
||||
0x84ec3c97da624ab4, 0xbd5af13bef0b113e, 0xa6274bbdd0fadd61, 0xecb1ad8aeacdd58e, 0xcfb11ead453994ba, 0x67de18eda5814af2, 0x81ceb32c4b43fcf4, 0x80eacf948770ced7,
|
||||
0xa2425ff75e14fc31, 0xa1258379a94d028d, 0xcad2f7f5359a3b3e, 0x96ee45813a04330, 0xfd87b5f28300ca0d, 0x8bca9d6e188853fc, 0x9e74d1b791e07e48, 0x775ea264cf55347e,
|
||||
0xc612062576589dda, 0x95364afe032a819e, 0xf79687aed3eec551, 0x3a83ddbd83f52205, 0x9abe14cd44753b52, 0xc4926a9672793543, 0xc16d9a0095928a27, 0x75b7053c0f178294,
|
||||
0xf1c90080baf72cb1, 0x5324c68b12dd6339, 0x971da05074da7bee, 0xd3f6fc16ebca5e04, 0xbce5086492111aea, 0x88f4bb1ca6bcf585, 0xec1e4a7db69561a5, 0x2b31e9e3d06c32e6,
|
||||
0x9392ee8e921d5d07, 0x3aff322e62439fd0, 0xb877aa3236a4b449, 0x9befeb9fad487c3, 0xe69594bec44de15b, 0x4c2ebe687989a9b4, 0x901d7cf73ab0acd9, 0xf9d37014bf60a11,
|
||||
0xb424dc35095cd80f, 0x538484c19ef38c95, 0xe12e13424bb40e13, 0x2865a5f206b06fba, 0x8cbccc096f5088cb, 0xf93f87b7442e45d4, 0xafebff0bcb24aafe, 0xf78f69a51539d749,
|
||||
0xdbe6fecebdedd5be, 0xb573440e5a884d1c, 0x89705f4136b4a597, 0x31680a88f8953031, 0xabcc77118461cefc, 0xfdc20d2b36ba7c3e, 0xd6bf94d5e57a42bc, 0x3d32907604691b4d,
|
||||
0x8637bd05af6c69b5, 0xa63f9a49c2c1b110, 0xa7c5ac471b478423, 0xfcf80dc33721d54, 0xd1b71758e219652b, 0xd3c36113404ea4a9, 0x83126e978d4fdf3b, 0x645a1cac083126ea,
|
||||
0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4, 0xcccccccccccccccc, 0xcccccccccccccccd, 0x8000000000000000, 0x0, 0xa000000000000000, 0x0,
|
||||
0xc800000000000000, 0x0, 0xfa00000000000000, 0x0, 0x9c40000000000000, 0x0, 0xc350000000000000, 0x0,
|
||||
0xf424000000000000, 0x0, 0x9896800000000000, 0x0, 0xbebc200000000000, 0x0, 0xee6b280000000000, 0x0,
|
||||
0x9502f90000000000, 0x0, 0xba43b74000000000, 0x0, 0xe8d4a51000000000, 0x0, 0x9184e72a00000000, 0x0,
|
||||
0xb5e620f480000000, 0x0, 0xe35fa931a0000000, 0x0, 0x8e1bc9bf04000000, 0x0, 0xb1a2bc2ec5000000, 0x0,
|
||||
0xde0b6b3a76400000, 0x0, 0x8ac7230489e80000, 0x0, 0xad78ebc5ac620000, 0x0, 0xd8d726b7177a8000, 0x0,
|
||||
0x878678326eac9000, 0x0, 0xa968163f0a57b400, 0x0, 0xd3c21bcecceda100, 0x0, 0x84595161401484a0, 0x0,
|
||||
0xa56fa5b99019a5c8, 0x0, 0xcecb8f27f4200f3a, 0x0, 0x813f3978f8940984, 0x4000000000000000, 0xa18f07d736b90be5, 0x5000000000000000,
|
||||
0xc9f2c9cd04674ede, 0xa400000000000000, 0xfc6f7c4045812296, 0x4d00000000000000, 0x9dc5ada82b70b59d, 0xf020000000000000, 0xc5371912364ce305, 0x6c28000000000000,
|
||||
0xf684df56c3e01bc6, 0xc732000000000000, 0x9a130b963a6c115c, 0x3c7f400000000000, 0xc097ce7bc90715b3, 0x4b9f100000000000, 0xf0bdc21abb48db20, 0x1e86d40000000000,
|
||||
0x96769950b50d88f4, 0x1314448000000000, 0xbc143fa4e250eb31, 0x17d955a000000000, 0xeb194f8e1ae525fd, 0x5dcfab0800000000, 0x92efd1b8d0cf37be, 0x5aa1cae500000000,
|
||||
0xb7abc627050305ad, 0xf14a3d9e40000000, 0xe596b7b0c643c719, 0x6d9ccd05d0000000, 0x8f7e32ce7bea5c6f, 0xe4820023a2000000, 0xb35dbf821ae4f38b, 0xdda2802c8a800000,
|
||||
0xe0352f62a19e306e, 0xd50b2037ad200000, 0x8c213d9da502de45, 0x4526f422cc340000, 0xaf298d050e4395d6, 0x9670b12b7f410000, 0xdaf3f04651d47b4c, 0x3c0cdd765f114000,
|
||||
0x88d8762bf324cd0f, 0xa5880a69fb6ac800, 0xab0e93b6efee0053, 0x8eea0d047a457a00, 0xd5d238a4abe98068, 0x72a4904598d6d880, 0x85a36366eb71f041, 0x47a6da2b7f864750,
|
||||
0xa70c3c40a64e6c51, 0x999090b65f67d924, 0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d, 0x82818f1281ed449f, 0xbff8f10e7a8921a4, 0xa321f2d7226895c7, 0xaff72d52192b6a0d,
|
||||
0xcbea6f8ceb02bb39, 0x9bf4f8a69f764490, 0xfee50b7025c36a08, 0x2f236d04753d5b4, 0x9f4f2726179a2245, 0x1d762422c946590, 0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef5,
|
||||
0xf8ebad2b84e0d58b, 0xd2e0898765a7deb2, 0x9b934c3b330c8577, 0x63cc55f49f88eb2f, 0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fb, 0xf316271c7fc3908a, 0x8bef464e3945ef7a,
|
||||
0x97edd871cfda3a56, 0x97758bf0e3cbb5ac, 0xbde94e8e43d0c8ec, 0x3d52eeed1cbea317, 0xed63a231d4c4fb27, 0x4ca7aaa863ee4bdd, 0x945e455f24fb1cf8, 0x8fe8caa93e74ef6a,
|
||||
0xb975d6b6ee39e436, 0xb3e2fd538e122b44, 0xe7d34c64a9c85d44, 0x60dbbca87196b616, 0x90e40fbeea1d3a4a, 0xbc8955e946fe31cd, 0xb51d13aea4a488dd, 0x6babab6398bdbe41,
|
||||
0xe264589a4dcdab14, 0xc696963c7eed2dd1, 0x8d7eb76070a08aec, 0xfc1e1de5cf543ca2, 0xb0de65388cc8ada8, 0x3b25a55f43294bcb, 0xdd15fe86affad912, 0x49ef0eb713f39ebe,
|
||||
0x8a2dbf142dfcc7ab, 0x6e3569326c784337, 0xacb92ed9397bf996, 0x49c2c37f07965404, 0xd7e77a8f87daf7fb, 0xdc33745ec97be906, 0x86f0ac99b4e8dafd, 0x69a028bb3ded71a3,
|
||||
0xa8acd7c0222311bc, 0xc40832ea0d68ce0c, 0xd2d80db02aabd62b, 0xf50a3fa490c30190, 0x83c7088e1aab65db, 0x792667c6da79e0fa, 0xa4b8cab1a1563f52, 0x577001b891185938,
|
||||
0xcde6fd5e09abcf26, 0xed4c0226b55e6f86, 0x80b05e5ac60b6178, 0x544f8158315b05b4, 0xa0dc75f1778e39d6, 0x696361ae3db1c721, 0xc913936dd571c84c, 0x3bc3a19cd1e38e9,
|
||||
0xfb5878494ace3a5f, 0x4ab48a04065c723, 0x9d174b2dcec0e47b, 0x62eb0d64283f9c76, 0xc45d1df942711d9a, 0x3ba5d0bd324f8394, 0xf5746577930d6500, 0xca8f44ec7ee36479,
|
||||
0x9968bf6abbe85f20, 0x7e998b13cf4e1ecb, 0xbfc2ef456ae276e8, 0x9e3fedd8c321a67e, 0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101e, 0x95d04aee3b80ece5, 0xbba1f1d158724a12,
|
||||
0xbb445da9ca61281f, 0x2a8a6e45ae8edc97, 0xea1575143cf97226, 0xf52d09d71a3293bd, 0x924d692ca61be758, 0x593c2626705f9c56, 0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836c,
|
||||
0xe498f455c38b997a, 0xb6dfb9c0f956447, 0x8edf98b59a373fec, 0x4724bd4189bd5eac, 0xb2977ee300c50fe7, 0x58edec91ec2cb657, 0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ed,
|
||||
0x8b865b215899f46c, 0xbd79e0d20082ee74, 0xae67f1e9aec07187, 0xecd8590680a3aa11, 0xda01ee641a708de9, 0xe80e6f4820cc9495, 0x884134fe908658b2, 0x3109058d147fdcdd,
|
||||
0xaa51823e34a7eede, 0xbd4b46f0599fd415, 0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91a, 0x850fadc09923329e, 0x3e2cf6bc604ddb0, 0xa6539930bf6bff45, 0x84db8346b786151c,
|
||||
0xcfe87f7cef46ff16, 0xe612641865679a63, 0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07e, 0xa26da3999aef7749, 0xe3be5e330f38f09d, 0xcb090c8001ab551c, 0x5cadf5bfd3072cc5,
|
||||
0xfdcb4fa002162a63, 0x73d9732fc7c8f7f6, 0x9e9f11c4014dda7e, 0x2867e7fddcdd9afa, 0xc646d63501a1511d, 0xb281e1fd541501b8, 0xf7d88bc24209a565, 0x1f225a7ca91a4226,
|
||||
0x9ae757596946075f, 0x3375788de9b06958, 0xc1a12d2fc3978937, 0x52d6b1641c83ae, 0xf209787bb47d6b84, 0xc0678c5dbd23a49a, 0x9745eb4d50ce6332, 0xf840b7ba963646e0,
|
||||
0xbd176620a501fbff, 0xb650e5a93bc3d898, 0xec5d3fa8ce427aff, 0xa3e51f138ab4cebe, 0x93ba47c980e98cdf, 0xc66f336c36b10137, 0xb8a8d9bbe123f017, 0xb80b0047445d4184,
|
||||
0xe6d3102ad96cec1d, 0xa60dc059157491e5, 0x9043ea1ac7e41392, 0x87c89837ad68db2f, 0xb454e4a179dd1877, 0x29babe4598c311fb, 0xe16a1dc9d8545e94, 0xf4296dd6fef3d67a,
|
||||
0x8ce2529e2734bb1d, 0x1899e4a65f58660c, 0xb01ae745b101e9e4, 0x5ec05dcff72e7f8f, 0xdc21a1171d42645d, 0x76707543f4fa1f73, 0x899504ae72497eba, 0x6a06494a791c53a8,
|
||||
0xabfa45da0edbde69, 0x487db9d17636892, 0xd6f8d7509292d603, 0x45a9d2845d3c42b6, 0x865b86925b9bc5c2, 0xb8a2392ba45a9b2, 0xa7f26836f282b732, 0x8e6cac7768d7141e,
|
||||
0xd1ef0244af2364ff, 0x3207d795430cd926, 0x8335616aed761f1f, 0x7f44e6bd49e807b8, 0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a6, 0xcd036837130890a1, 0x36dba887c37a8c0f,
|
||||
0x802221226be55a64, 0xc2494954da2c9789, 0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6c, 0xc83553c5c8965d3d, 0x6f92829494e5acc7, 0xfa42a8b73abbf48c, 0xcb772339ba1f17f9,
|
||||
0x9c69a97284b578d7, 0xff2a760414536efb, 0xc38413cf25e2d70d, 0xfef5138519684aba, 0xf46518c2ef5b8cd1, 0x7eb258665fc25d69, 0x98bf2f79d5993802, 0xef2f773ffbd97a61,
|
||||
0xbeeefb584aff8603, 0xaafb550ffacfd8fa, 0xeeaaba2e5dbf6784, 0x95ba2a53f983cf38, 0x952ab45cfa97a0b2, 0xdd945a747bf26183, 0xba756174393d88df, 0x94f971119aeef9e4,
|
||||
0xe912b9d1478ceb17, 0x7a37cd5601aab85d, 0x91abb422ccb812ee, 0xac62e055c10ab33a, 0xb616a12b7fe617aa, 0x577b986b314d6009, 0xe39c49765fdf9d94, 0xed5a7e85fda0b80b,
|
||||
0x8e41ade9fbebc27d, 0x14588f13be847307, 0xb1d219647ae6b31c, 0x596eb2d8ae258fc8, 0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bb, 0x8aec23d680043bee, 0x25de7bb9480d5854,
|
||||
0xada72ccc20054ae9, 0xaf561aa79a10ae6a, 0xd910f7ff28069da4, 0x1b2ba1518094da04, 0x87aa9aff79042286, 0x90fb44d2f05d0842, 0xa99541bf57452b28, 0x353a1607ac744a53,
|
||||
0xd3fa922f2d1675f2, 0x42889b8997915ce8, 0x847c9b5d7c2e09b7, 0x69956135febada11, 0xa59bc234db398c25, 0x43fab9837e699095, 0xcf02b2c21207ef2e, 0x94f967e45e03f4bb,
|
||||
0x8161afb94b44f57d, 0x1d1be0eebac278f5, 0xa1ba1ba79e1632dc, 0x6462d92a69731732, 0xca28a291859bbf93, 0x7d7b8f7503cfdcfe, 0xfcb2cb35e702af78, 0x5cda735244c3d43e,
|
||||
0x9defbf01b061adab, 0x3a0888136afa64a7, 0xc56baec21c7a1916, 0x88aaa1845b8fdd0, 0xf6c69a72a3989f5b, 0x8aad549e57273d45, 0x9a3c2087a63f6399, 0x36ac54e2f678864b,
|
||||
0xc0cb28a98fcf3c7f, 0x84576a1bb416a7dd, 0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d5, 0x969eb7c47859e743, 0x9f644ae5a4b1b325, 0xbc4665b596706114, 0x873d5d9f0dde1fee,
|
||||
0xeb57ff22fc0c7959, 0xa90cb506d155a7ea, 0x9316ff75dd87cbd8, 0x9a7f12442d588f2, 0xb7dcbf5354e9bece, 0xc11ed6d538aeb2f, 0xe5d3ef282a242e81, 0x8f1668c8a86da5fa,
|
||||
0x8fa475791a569d10, 0xf96e017d694487bc, 0xb38d92d760ec4455, 0x37c981dcc395a9ac, 0xe070f78d3927556a, 0x85bbe253f47b1417, 0x8c469ab843b89562, 0x93956d7478ccec8e,
|
||||
0xaf58416654a6babb, 0x387ac8d1970027b2, 0xdb2e51bfe9d0696a, 0x6997b05fcc0319e, 0x88fcf317f22241e2, 0x441fece3bdf81f03, 0xab3c2fddeeaad25a, 0xd527e81cad7626c3,
|
||||
0xd60b3bd56a5586f1, 0x8a71e223d8d3b074, 0x85c7056562757456, 0xf6872d5667844e49, 0xa738c6bebb12d16c, 0xb428f8ac016561db, 0xd106f86e69d785c7, 0xe13336d701beba52,
|
||||
0x82a45b450226b39c, 0xecc0024661173473, 0xa34d721642b06084, 0x27f002d7f95d0190, 0xcc20ce9bd35c78a5, 0x31ec038df7b441f4, 0xff290242c83396ce, 0x7e67047175a15271,
|
||||
0x9f79a169bd203e41, 0xf0062c6e984d386, 0xc75809c42c684dd1, 0x52c07b78a3e60868, 0xf92e0c3537826145, 0xa7709a56ccdf8a82, 0x9bbcc7a142b17ccb, 0x88a66076400bb691,
|
||||
0xc2abf989935ddbfe, 0x6acff893d00ea435, 0xf356f7ebf83552fe, 0x583f6b8c4124d43, 0x98165af37b2153de, 0xc3727a337a8b704a, 0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5c,
|
||||
0xeda2ee1c7064130c, 0x1162def06f79df73, 0x9485d4d1c63e8be7, 0x8addcb5645ac2ba8, 0xb9a74a0637ce2ee1, 0x6d953e2bd7173692, 0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0437,
|
||||
0x910ab1d4db9914a0, 0x1d9c9892400a22a2, 0xb54d5e4a127f59c8, 0x2503beb6d00cab4b, 0xe2a0b5dc971f303a, 0x2e44ae64840fd61d, 0x8da471a9de737e24, 0x5ceaecfed289e5d2,
|
||||
0xb10d8e1456105dad, 0x7425a83e872c5f47, 0xdd50f1996b947518, 0xd12f124e28f77719, 0x8a5296ffe33cc92f, 0x82bd6b70d99aaa6f, 0xace73cbfdc0bfb7b, 0x636cc64d1001550b,
|
||||
0xd8210befd30efa5a, 0x3c47f7e05401aa4e, 0x8714a775e3e95c78, 0x65acfaec34810a71, 0xa8d9d1535ce3b396, 0x7f1839a741a14d0d, 0xd31045a8341ca07c, 0x1ede48111209a050,
|
||||
0x83ea2b892091e44d, 0x934aed0aab460432, 0xa4e4b66b68b65d60, 0xf81da84d5617853f, 0xce1de40642e3f4b9, 0x36251260ab9d668e, 0x80d2ae83e9ce78f3, 0xc1d72b7c6b426019,
|
||||
0xa1075a24e4421730, 0xb24cf65b8612f81f, 0xc94930ae1d529cfc, 0xdee033f26797b627, 0xfb9b7cd9a4a7443c, 0x169840ef017da3b1, 0x9d412e0806e88aa5, 0x8e1f289560ee864e,
|
||||
0xc491798a08a2ad4e, 0xf1a6f2bab92a27e2, 0xf5b5d7ec8acb58a2, 0xae10af696774b1db, 0x9991a6f3d6bf1765, 0xacca6da1e0a8ef29, 0xbff610b0cc6edd3f, 0x17fd090a58d32af3,
|
||||
0xeff394dcff8a948e, 0xddfc4b4cef07f5b0, 0x95f83d0a1fb69cd9, 0x4abdaf101564f98e, 0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f1, 0xea53df5fd18d5513, 0x84c86189216dc5ed,
|
||||
0x92746b9be2f8552c, 0x32fd3cf5b4e49bb4, 0xb7118682dbb66a77, 0x3fbc8c33221dc2a1, 0xe4d5e82392a40515, 0xfabaf3feaa5334a, 0x8f05b1163ba6832d, 0x29cb4d87f2a7400e,
|
||||
0xb2c71d5bca9023f8, 0x743e20e9ef511012, 0xdf78e4b2bd342cf6, 0x914da9246b255416, 0x8bab8eefb6409c1a, 0x1ad089b6c2f7548e, 0xae9672aba3d0c320, 0xa184ac2473b529b1,
|
||||
0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741e, 0x8865899617fb1871, 0x7e2fa67c7a658892, 0xaa7eebfb9df9de8d, 0xddbb901b98feeab7, 0xd51ea6fa85785631, 0x552a74227f3ea565,
|
||||
0x8533285c936b35de, 0xd53a88958f87275f, 0xa67ff273b8460356, 0x8a892abaf368f137, 0xd01fef10a657842c, 0x2d2b7569b0432d85, 0x8213f56a67f6b29b, 0x9c3b29620e29fc73,
|
||||
0xa298f2c501f45f42, 0x8349f3ba91b47b8f, 0xcb3f2f7642717713, 0x241c70a936219a73, 0xfe0efb53d30dd4d7, 0xed238cd383aa0110, 0x9ec95d1463e8a506, 0xf4363804324a40aa,
|
||||
0xc67bb4597ce2ce48, 0xb143c6053edcd0d5, 0xf81aa16fdc1b81da, 0xdd94b7868e94050a, 0x9b10a4e5e9913128, 0xca7cf2b4191c8326, 0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f0,
|
||||
0xf24a01a73cf2dccf, 0xbc633b39673c8cec, 0x976e41088617ca01, 0xd5be0503e085d813, 0xbd49d14aa79dbc82, 0x4b2d8644d8a74e18, 0xec9c459d51852ba2, 0xddf8e7d60ed1219e,
|
||||
0x93e1ab8252f33b45, 0xcabb90e5c942b503, 0xb8da1662e7b00a17, 0x3d6a751f3b936243, 0xe7109bfba19c0c9d, 0xcc512670a783ad4, 0x906a617d450187e2, 0x27fb2b80668b24c5,
|
||||
0xb484f9dc9641e9da, 0xb1f9f660802dedf6, 0xe1a63853bbd26451, 0x5e7873f8a0396973, 0x8d07e33455637eb2, 0xdb0b487b6423e1e8, 0xb049dc016abc5e5f, 0x91ce1a9a3d2cda62,
|
||||
0xdc5c5301c56b75f7, 0x7641a140cc7810fb, 0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9d, 0xac2820d9623bf429, 0x546345fa9fbdcd44, 0xd732290fbacaf133, 0xa97c177947ad4095,
|
||||
0x867f59a9d4bed6c0, 0x49ed8eabcccc485d, 0xa81f301449ee8c70, 0x5c68f256bfff5a74, 0xd226fc195c6a2f8c, 0x73832eec6fff3111, 0x83585d8fd9c25db7, 0xc831fd53c5ff7eab,
|
||||
0xa42e74f3d032f525, 0xba3e7ca8b77f5e55, 0xcd3a1230c43fb26f, 0x28ce1bd2e55f35eb, 0x80444b5e7aa7cf85, 0x7980d163cf5b81b3, 0xa0555e361951c366, 0xd7e105bcc332621f,
|
||||
0xc86ab5c39fa63440, 0x8dd9472bf3fefaa7, 0xfa856334878fc150, 0xb14f98f6f0feb951, 0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d3, 0xc3b8358109e84f07, 0xa862f80ec4700c8,
|
||||
0xf4a642e14c6262c8, 0xcd27bb612758c0fa, 0x98e7e9cccfbd7dbd, 0x8038d51cb897789c, 0xbf21e44003acdd2c, 0xe0470a63e6bd56c3, 0xeeea5d5004981478, 0x1858ccfce06cac74,
|
||||
0x95527a5202df0ccb, 0xf37801e0c43ebc8, 0xbaa718e68396cffd, 0xd30560258f54e6ba, 0xe950df20247c83fd, 0x47c6b82ef32a2069, 0x91d28b7416cdd27e, 0x4cdc331d57fa5441,
|
||||
0xb6472e511c81471d, 0xe0133fe4adf8e952, 0xe3d8f9e563a198e5, 0x58180fddd97723a6, 0x8e679c2f5e44ff8f, 0x570f09eaa7ea7648,
|
||||
};
|
||||
|
||||
|
||||
/* Maximum mantissa for fast path: 2^53 */
|
||||
#define MAX_MANTISSA_FAST_PATH 9007199254740992ULL /* 2^53 */
|
||||
|
||||
|
|
@ -159,6 +348,190 @@ static inline uint32_t parse_eight_digits_swar(uint64_t val) {
|
|||
return (uint32_t)val;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Eisel-Lemire algorithm — core (compute_float / am_to_double).
|
||||
*
|
||||
* Given a decimal mantissa `w` (≤ 19 digits, fits in uint64) and exponent `q`,
|
||||
* compute the correctly-rounded `double` representing `w * 10^q`. Internally:
|
||||
*
|
||||
* 1. Shift `w` so its leading bit is set (full 64-bit mantissa).
|
||||
* 2. Multiply by the 128-bit precomputed power-of-five entry above.
|
||||
* 3. Extract the 53-bit mantissa from the high 64 bits of the product, with
|
||||
* one extra bit for round-to-nearest-even.
|
||||
* 4. Apply the round-half-to-even rule, including the rare power-of-2 tie
|
||||
* case that needs a second-pass check.
|
||||
*
|
||||
* For the 19-digit / |q| ≤ 22 input range the result is provably bit-exact
|
||||
* with strtod() (Mushtak & Lemire, "Fast Number Parsing Without Fallback").
|
||||
* The caller falls back to strtod() if compute_float() signals indeterminate
|
||||
* (we never trigger that branch with parse_number_string's bounded inputs).
|
||||
*
|
||||
* Ported from fast_float by Daniel Lemire & Joao Paulo Magalhaes
|
||||
* (MIT-licensed, https://github.com/fastfloat/fast_float — decimal_to_binary.h
|
||||
* and float_common.h). C++ template machinery dropped in favour of a
|
||||
* double-only specialisation; struct layouts kept to ease future review.
|
||||
* ---------------------------------------------------------------------------- */
|
||||
|
||||
/* IEEE-754 binary64 constants (mirrors fast_float's binary_format<double>). */
|
||||
#define DOUBLE_MANTISSA_EXPLICIT_BITS 52
|
||||
#define DOUBLE_MIN_EXPONENT_ROUND_EVEN -4
|
||||
#define DOUBLE_MAX_EXPONENT_ROUND_EVEN 23
|
||||
#define DOUBLE_MINIMUM_EXPONENT -1023
|
||||
#define DOUBLE_INFINITE_POWER 0x7FF
|
||||
|
||||
/* 128-bit unsigned, little-endian: low holds bits [0..63]. */
|
||||
typedef struct {
|
||||
uint64_t low;
|
||||
uint64_t high;
|
||||
} value128;
|
||||
|
||||
/* Result of compute_float(): a 53-bit mantissa and a biased binary exponent.
|
||||
* power2 < 0 signals indeterminate (caller should fall back to strtod()). */
|
||||
typedef struct {
|
||||
uint64_t mantissa;
|
||||
int32_t power2;
|
||||
} adjusted_mantissa;
|
||||
|
||||
/* `__builtin_clzll` is undefined on input 0 — caller guarantees v > 0. */
|
||||
static inline int leading_zeroes_u64(uint64_t v) {
|
||||
return __builtin_clzll(v);
|
||||
}
|
||||
|
||||
/* 64x64 -> 128 multiplication. __uint128_t is available on every 64-bit
|
||||
* target Redis supports (gated explicitly in the call site). */
|
||||
static inline value128 full_multiplication(uint64_t a, uint64_t b) {
|
||||
value128 r;
|
||||
#ifdef __SIZEOF_INT128__
|
||||
__uint128_t prod = (__uint128_t)a * (__uint128_t)b;
|
||||
r.low = (uint64_t)prod;
|
||||
r.high = (uint64_t)(prod >> 64);
|
||||
#else
|
||||
/* 32-bit fallback: split each operand into two 32-bit halves. */
|
||||
uint64_t a_lo = (uint32_t)a, a_hi = a >> 32;
|
||||
uint64_t b_lo = (uint32_t)b, b_hi = b >> 32;
|
||||
uint64_t ll = a_lo * b_lo;
|
||||
uint64_t lh = a_lo * b_hi;
|
||||
uint64_t hl = a_hi * b_lo;
|
||||
uint64_t hh = a_hi * b_hi;
|
||||
uint64_t mid = (ll >> 32) + (uint32_t)lh + (uint32_t)hl;
|
||||
r.low = (mid << 32) | (uint32_t)ll;
|
||||
r.high = hh + (lh >> 32) + (hl >> 32) + (mid >> 32);
|
||||
#endif
|
||||
return r;
|
||||
}
|
||||
|
||||
/* For q in (-400, 350), this approximates floor(log2(5^q)) + q + 63
|
||||
* (or -ceil(log2(5^|q|)) + q + 63 for negative q). Used to derive power2. */
|
||||
static inline int32_t eisel_lemire_power(int32_t q) {
|
||||
return (((152170 + 65536) * q) >> 16) + 63;
|
||||
}
|
||||
|
||||
/* 128-bit approximation of `w * 5^q`. The optional fixup multiplies by the
|
||||
* second (extension) entry of the power-of-five table when the high half is
|
||||
* close to a rounding boundary. Mathematical proof of sufficiency: see
|
||||
* Mushtak & Lemire, "Fast Number Parsing Without Fallback". */
|
||||
static inline value128 compute_product_approximation_d(int64_t q, uint64_t w) {
|
||||
int index = 2 * (int)(q - EISEL_LEMIRE_SMALLEST_POWER_OF_FIVE);
|
||||
value128 firstproduct = full_multiplication(w, power_of_five_128[index]);
|
||||
/* For double, bit_precision = mantissa_explicit_bits (52) + 3 = 55. */
|
||||
const uint64_t precision_mask =
|
||||
(uint64_t)0xFFFFFFFFFFFFFFFFULL >> 55;
|
||||
if ((firstproduct.high & precision_mask) == precision_mask) {
|
||||
value128 secondproduct =
|
||||
full_multiplication(w, power_of_five_128[index + 1]);
|
||||
firstproduct.low += secondproduct.high;
|
||||
if (secondproduct.high > firstproduct.low) {
|
||||
firstproduct.high++;
|
||||
}
|
||||
}
|
||||
return firstproduct;
|
||||
}
|
||||
|
||||
/* Eisel-Lemire main: compute a correctly-rounded representation of w * 10^q.
|
||||
* Returns an `adjusted_mantissa`. Special outputs:
|
||||
* - mantissa == 0 && power2 == 0: result is +/-0
|
||||
* - power2 == DOUBLE_INFINITE_POWER && mantissa == 0: result is infinity
|
||||
* - power2 < 0: indeterminate (caller should fall back to strtod()). With
|
||||
* parse_number_string()'s bounded mantissa (<= 19 digits), this branch
|
||||
* is unreachable, but we keep the signature for safety.
|
||||
*/
|
||||
static adjusted_mantissa compute_float_d(int64_t q, uint64_t w) {
|
||||
adjusted_mantissa answer;
|
||||
|
||||
if (w == 0 || q < EISEL_LEMIRE_SMALLEST_POWER_OF_FIVE) {
|
||||
answer.power2 = 0;
|
||||
answer.mantissa = 0;
|
||||
return answer;
|
||||
}
|
||||
if (q > EISEL_LEMIRE_LARGEST_POWER_OF_FIVE) {
|
||||
answer.power2 = DOUBLE_INFINITE_POWER;
|
||||
answer.mantissa = 0;
|
||||
return answer;
|
||||
}
|
||||
|
||||
/* Renormalise w so its top bit is set. */
|
||||
int lz = leading_zeroes_u64(w);
|
||||
w <<= lz;
|
||||
|
||||
value128 product = compute_product_approximation_d(q, w);
|
||||
|
||||
int upperbit = (int)(product.high >> 63);
|
||||
int shift = upperbit + 64 - DOUBLE_MANTISSA_EXPLICIT_BITS - 3;
|
||||
|
||||
answer.mantissa = product.high >> shift;
|
||||
answer.power2 = (int32_t)(eisel_lemire_power((int32_t)q) + upperbit - lz - DOUBLE_MINIMUM_EXPONENT);
|
||||
|
||||
if (answer.power2 <= 0) {
|
||||
/* Subnormal path. */
|
||||
if (-answer.power2 + 1 >= 64) {
|
||||
/* More than 64 bits below minimum exponent — definitely zero. */
|
||||
answer.power2 = 0;
|
||||
answer.mantissa = 0;
|
||||
return answer;
|
||||
}
|
||||
/* Safe: -answer.power2 + 1 < 64. */
|
||||
answer.mantissa >>= -answer.power2 + 1;
|
||||
answer.mantissa += (answer.mantissa & 1); /* round up */
|
||||
answer.mantissa >>= 1;
|
||||
/* If post-rounding the value crosses back into the normal range, mark
|
||||
* it normal (power2 = 1) rather than subnormal (power2 = 0). */
|
||||
answer.power2 = (answer.mantissa < ((uint64_t)1 << DOUBLE_MANTISSA_EXPLICIT_BITS)) ? 0 : 1;
|
||||
return answer;
|
||||
}
|
||||
|
||||
/* Normal path: handle the round-half-to-even tie case. */
|
||||
if ((product.low <= 1) &&
|
||||
(q >= DOUBLE_MIN_EXPONENT_ROUND_EVEN) &&
|
||||
(q <= DOUBLE_MAX_EXPONENT_ROUND_EVEN) &&
|
||||
((answer.mantissa & 3) == 1)) {
|
||||
if ((answer.mantissa << shift) == product.high) {
|
||||
answer.mantissa &= ~(uint64_t)1; /* clear LSB so we round down */
|
||||
}
|
||||
}
|
||||
answer.mantissa += (answer.mantissa & 1);
|
||||
answer.mantissa >>= 1;
|
||||
if (answer.mantissa >= ((uint64_t)2 << DOUBLE_MANTISSA_EXPLICIT_BITS)) {
|
||||
answer.mantissa = (uint64_t)1 << DOUBLE_MANTISSA_EXPLICIT_BITS;
|
||||
answer.power2++;
|
||||
}
|
||||
answer.mantissa &= ~((uint64_t)1 << DOUBLE_MANTISSA_EXPLICIT_BITS);
|
||||
if (answer.power2 >= DOUBLE_INFINITE_POWER) {
|
||||
answer.power2 = DOUBLE_INFINITE_POWER;
|
||||
answer.mantissa = 0;
|
||||
}
|
||||
return answer;
|
||||
}
|
||||
|
||||
/* Pack adjusted_mantissa back to a double via IEEE-754 bit layout. */
|
||||
static inline double am_to_double(int negative, adjusted_mantissa am) {
|
||||
uint64_t word = am.mantissa;
|
||||
word |= (uint64_t)am.power2 << DOUBLE_MANTISSA_EXPLICIT_BITS;
|
||||
if (negative) word |= (uint64_t)1 << 63;
|
||||
double value;
|
||||
memcpy(&value, &word, sizeof(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
/* Parse a decimal number string into components.
|
||||
* This follows the fast_float algorithm closely. */
|
||||
static inline int parse_number_string(const char *p, const char *pend, double *result, const char **endptr) {
|
||||
|
|
@ -261,65 +634,42 @@ static inline int parse_number_string(const char *p, const char *pend, double *r
|
|||
if (digit_count > MAX_DIGITS) return 0;
|
||||
}
|
||||
|
||||
/* Check if we're within fast path bounds */
|
||||
if (exponent < MIN_EXPONENT_FAST_PATH) return 0;
|
||||
if (exponent > MAX_EXPONENT_FAST_PATH) return 0;
|
||||
|
||||
/* Pick the conversion path. Two regimes:
|
||||
* Clinger fast path: small mantissa (<= 2^53) and small |exp| (<= 22).
|
||||
* One double multiply or divide; cheapest, exact by construction.
|
||||
* Eisel-Lemire: large mantissa or wide exponent range (full double
|
||||
* domain). Slightly slower per call (128-bit multiply + table lookup)
|
||||
* but correctly-rounded by the Mushtak-Lemire proof.
|
||||
* Inputs outside both ranges fall back to strtod() (caller of this fn). */
|
||||
double value;
|
||||
if (mantissa <= MAX_MANTISSA_FAST_PATH) {
|
||||
if (mantissa <= MAX_MANTISSA_FAST_PATH &&
|
||||
exponent >= MIN_EXPONENT_FAST_PATH &&
|
||||
exponent <= MAX_EXPONENT_FAST_PATH)
|
||||
{
|
||||
/* Clinger fast path: all operands exact in double precision,
|
||||
* single multiply/divide produces a correctly-rounded result. */
|
||||
value = (double)mantissa;
|
||||
if (exponent < 0) value = value / powers_of_ten[-exponent];
|
||||
else if (exponent > 0) value = value * powers_of_ten[exponent];
|
||||
if (negative) value = -value;
|
||||
} else {
|
||||
#ifdef __SIZEOF_INT128__
|
||||
/* Widened fast path for 17-19 significant-digit mantissas.
|
||||
*
|
||||
* (double)mantissa alone loses up to 11 bits when mantissa > 2^53,
|
||||
* so the existing Clinger path would yield up to 1 ULP vs strtod.
|
||||
* We recover full precision by doing the multiply/divide in 128-bit
|
||||
* integer arithmetic (correctly-rounded by construction). Cases
|
||||
* outside the supported exponent range fall through to strtod.
|
||||
*
|
||||
* Requires __uint128_t (GCC/Clang builtin, available on every 64-bit
|
||||
* target Redis supports). 32-bit builds take the strtod() fallback. */
|
||||
if (exponent < -19 || exponent > 19) return 0;
|
||||
/* Eisel-Lemire path. Replaces a previously hand-rolled widened branch
|
||||
* (`(double)hi * 2^64 + (double)lo` shortcut) that produced ±1 ULP
|
||||
* mismatches vs strtod() on inputs like 9007199255094284e-19 and
|
||||
* 2489830482329185244e1. compute_float_d is bit-exact with strtod()
|
||||
* for every input parse_number_string can produce. */
|
||||
if (exponent < EISEL_LEMIRE_SMALLEST_POWER_OF_FIVE || exponent > EISEL_LEMIRE_LARGEST_POWER_OF_FIVE)
|
||||
return 0;
|
||||
|
||||
if (exponent >= 0) {
|
||||
/* (mantissa * 10^e) fits in 128 bits. Convert exactly: the
|
||||
* single (double) cast from __uint128_t rounds to nearest. */
|
||||
__uint128_t prod = (__uint128_t)mantissa * (uint64_t)powers_of_ten[exponent];
|
||||
uint64_t hi = (uint64_t)(prod >> 64);
|
||||
uint64_t lo = (uint64_t)prod;
|
||||
/* (double)hi * 2^64 has no rounding error (hi up to 2^64-1 rounds
|
||||
* once, then * 2^64 is exact). Adding lo rounds once. Total:
|
||||
* matches strtod on every tested case with e in [0,19]. */
|
||||
value = (double)hi * 18446744073709551616.0 + (double)lo;
|
||||
} else {
|
||||
/* mantissa / 10^|e|: scale numerator up by 2^64 before integer
|
||||
* division to preserve precision, then descale by multiplying by
|
||||
* 2^-64 (exact power-of-two scaling, does not round). The single
|
||||
* (double) cast of the integer quotient produces IEEE round-to-
|
||||
* nearest-even, matching strtod() bit-exactly for every tested
|
||||
* 16-19 significant digit case. */
|
||||
uint64_t divisor = (uint64_t)powers_of_ten[-exponent];
|
||||
__uint128_t scaled = (__uint128_t)mantissa << 64;
|
||||
__uint128_t q = scaled / divisor;
|
||||
uint64_t hi = (uint64_t)(q >> 64);
|
||||
uint64_t lo = (uint64_t)q;
|
||||
value = ((double)hi * 18446744073709551616.0 + (double)lo)
|
||||
* 5.421010862427522170037e-20; /* 2^-64 */
|
||||
}
|
||||
#else
|
||||
/* 32-bit target without __uint128_t: fall through to the strtod()
|
||||
* fallback. Correctness is preserved (it's the same path that shipped
|
||||
* in 8.8-M02); only the perf gain is 64-bit-target-specific. */
|
||||
return 0;
|
||||
#endif
|
||||
adjusted_mantissa am = compute_float_d(exponent, mantissa);
|
||||
/* power2 < 0 would mean indeterminate (caller should fall back to
|
||||
* strtod). With our bounded mantissa (<= 19 digits) this branch is
|
||||
* unreachable per the Mushtak-Lemire proof, but we keep the guard so
|
||||
* any future caller that supplies a larger mantissa stays correct. */
|
||||
if (am.power2 < 0) return 0;
|
||||
value = am_to_double(negative, am);
|
||||
}
|
||||
|
||||
if (negative) value = -value;
|
||||
*result = value;
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -524,9 +874,87 @@ int fastFloatTest(int argc, char **argv, int flags) {
|
|||
/* Negative numbers exercising the widened path */
|
||||
{"-0.49606648747577575", -0.49606648747577575},
|
||||
{"-9007199254740993", -9007199254740992.0},
|
||||
|
||||
/* Eisel-Lemire rounding-boundary cases.
|
||||
* Reported by @vitahlin on #14661 against the previous
|
||||
* `(double)hi * 2^64 + (double)lo` widened branch which
|
||||
* double-rounded the 128-bit product. Both must now match
|
||||
* strtod() exactly. */
|
||||
{"9007199255094284e-19", 9007199255094284e-19}, /* was -1 ULP */
|
||||
{"2489830482329185244e1", 2489830482329185244e1}, /* was +1 ULP */
|
||||
|
||||
/* Subnormal boundaries (Eisel-Lemire's subnormal branch). */
|
||||
{"5e-324", 5e-324}, /* smallest pos subnormal */
|
||||
{"4.9e-324", 5e-324}, /* below half: rounds up */
|
||||
{"2.2250738585072009e-308", 2.2250738585072009e-308}, /* largest subnormal */
|
||||
{"2.2250738585072014e-308", 2.2250738585072014e-308}, /* smallest normal */
|
||||
{"1e-323", 1e-323},
|
||||
|
||||
/* Round-half-to-even ties: post-Clinger range, hits compute_float_d
|
||||
* tie path (product.low <= 1, q in [-4, 23], mantissa & 3 == 1). */
|
||||
{"5497558138880", 5497558138880.0}, /* 2^42 + 2^33 boundary */
|
||||
{"5e-22", 5e-22},
|
||||
{"7.038531e-26", 7.038531e-26},
|
||||
{"4503599627475501e-10", 4503599627475501e-10}, /* near 2^52 */
|
||||
|
||||
/* Largest finite double + overflow. */
|
||||
{"1.7976931348623157e308", 1.7976931348623157e308}, /* DBL_MAX */
|
||||
{"1.7976931348623158e308", 1.7976931348623157e308}, /* nearest is DBL_MAX */
|
||||
{"1e308", 1e308},
|
||||
|
||||
/* Wide exponent range now reachable via Eisel-Lemire (previously
|
||||
* fell to strtod). */
|
||||
{"1.234567890123456e100", 1.234567890123456e100},
|
||||
{"9.999999999999999e99", 9.999999999999999e99},
|
||||
{"1e-300", 1e-300},
|
||||
{"1.7e-300", 1.7e-300},
|
||||
|
||||
/* Repunit / many-9 mantissas — adjacent-double tie territory. */
|
||||
{"9999999999999998", 9999999999999998.0},
|
||||
{"99999999999999999", 1e17},
|
||||
};
|
||||
run_ff_tests(decimal_ok, COUNTOF(decimal_ok), 0);
|
||||
|
||||
/* Differential cross-check: every accepted input must produce the
|
||||
* exact same bits as libc strtod(). Hand-picked hard cases covering
|
||||
* every code path in compute_float_d (subnormal branch, round-half-
|
||||
* to-even tie path, near-infinity, repunit mantissa, wide exponent). */
|
||||
{
|
||||
static const char *diff_inputs[] = {
|
||||
/* Boundary classics around 2^53. */
|
||||
"9007199254740992", "9007199254740993", "9007199254740994",
|
||||
"9007199254740995", "9007199254740996",
|
||||
/* Limits of finite double. */
|
||||
"1.7976931348623157e308", "2.2250738585072014e-308",
|
||||
"5e-324", "1e-323", "4.9406564584124654e-324",
|
||||
/* The two reproducer inputs the previous widened branch missed. */
|
||||
"9007199255094284e-19", "2489830482329185244e1",
|
||||
/* Mushtak-Lemire stress range — 19-digit mantissas. */
|
||||
"1234567890123456789e0", "1234567890123456789e-5",
|
||||
"1234567890123456789e5", "9999999999999999e19",
|
||||
/* Common scientific constants — mid-exponent sanity. */
|
||||
"3.141592653589793", "2.718281828459045",
|
||||
"1.4142135623730951e150", "6.022140857e23",
|
||||
"1.602176634e-19", "9.10938356e-31",
|
||||
};
|
||||
for (int i = 0; i < COUNTOF(diff_inputs); i++) {
|
||||
const char *s = diff_inputs[i];
|
||||
char *fend, *lend;
|
||||
errno = 0;
|
||||
double got = fast_float_strtod(s, strlen(s), &fend);
|
||||
errno = 0;
|
||||
double libc = strtod(s, &lend);
|
||||
uint64_t gb, lb;
|
||||
memcpy(&gb, &got, sizeof(gb));
|
||||
memcpy(&lb, &libc, sizeof(lb));
|
||||
char descr[160];
|
||||
snprintf(descr, sizeof(descr),
|
||||
"differential vs strtod: \"%s\" ff=0x%016llx libc=0x%016llx",
|
||||
s, (unsigned long long)gb, (unsigned long long)lb);
|
||||
test_cond(descr, gb == lb);
|
||||
}
|
||||
}
|
||||
|
||||
/* No valid prefix for full buffer, or trailing junk. */
|
||||
ff_testcase decimal_bad[] = {
|
||||
{"1abc", 1.0},
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@
|
|||
#include "server.h"
|
||||
#include <math.h>
|
||||
|
||||
#ifdef ENABLE_GCRA
|
||||
|
||||
/* GCRA algorithm for rate limiting.
|
||||
* Implementation is heavily based on the implementation of (redis-cell)
|
||||
* [https://github.com/brandur/redis-cell] by (brandur)[https://github.com/brandur].
|
||||
|
|
@ -278,3 +280,5 @@ robj *gcraDup(robj *o) {
|
|||
getLongLongFromGCRAObject(o, &val);
|
||||
return createGCRAObject(val);
|
||||
}
|
||||
|
||||
#endif /* ENABLE_GCRA */
|
||||
|
|
|
|||
|
|
@ -13,11 +13,6 @@
|
|||
#include "cluster.h"
|
||||
#include <sys/resource.h>
|
||||
|
||||
static inline int nearestNextPowerOf2(unsigned int count) {
|
||||
if (count <= 1) return 1;
|
||||
return 1 << (32 - __builtin_clz(count-1));
|
||||
}
|
||||
|
||||
/* Comparison function for qsort to sort slot indices */
|
||||
static inline int slotCompare(const void *a, const void *b) {
|
||||
return (*(const int *)a) - (*(const int *)b);
|
||||
|
|
|
|||
|
|
@ -207,6 +207,9 @@ size_t lazyfreeGetFreeEffort(robj *key, robj *obj, int dbid) {
|
|||
/* If the module's free_effort returns 0, we will use asynchronous free
|
||||
* memory by default. */
|
||||
return effort == 0 ? ULONG_MAX : effort;
|
||||
} else if (obj->type == OBJ_ARRAY) {
|
||||
redisArray *ar = obj->ptr;
|
||||
return arCount(ar);
|
||||
} else {
|
||||
return 1; /* Everything else is a single allocation. */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4254,7 +4254,10 @@ int RM_KeyType(RedisModuleKey *key) {
|
|||
case OBJ_HASH: return REDISMODULE_KEYTYPE_HASH;
|
||||
case OBJ_MODULE: return REDISMODULE_KEYTYPE_MODULE;
|
||||
case OBJ_STREAM: return REDISMODULE_KEYTYPE_STREAM;
|
||||
#ifdef ENABLE_GCRA
|
||||
case OBJ_GCRA: return REDISMODULE_KEYTYPE_GCRA;
|
||||
#endif
|
||||
case OBJ_ARRAY: return REDISMODULE_KEYTYPE_ARRAY;
|
||||
default: return REDISMODULE_KEYTYPE_EMPTY;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1181,6 +1181,18 @@ void addReplyLongLongFromStr(client *c, robj *str) {
|
|||
addReplyProto(c,"\r\n",2);
|
||||
}
|
||||
|
||||
/* Reply with unsigned 64-bit value. Uses integer reply when value fits in
|
||||
* signed long long, otherwise big number (RESP3) or bulk string (RESP2). */
|
||||
void addReplyUnsignedLongLong(client *c, uint64_t v) {
|
||||
if (v <= (uint64_t)LLONG_MAX) {
|
||||
addReplyLongLong(c, (long long)v);
|
||||
} else {
|
||||
char buf[LONG_STR_SIZE];
|
||||
int len = ull2string(buf, sizeof(buf), v);
|
||||
addReplyBigNum(c, buf, len);
|
||||
}
|
||||
}
|
||||
|
||||
void addReplyAggregateLen(client *c, long length, int prefix) {
|
||||
serverAssert(length >= 0);
|
||||
if (_prepareClientToWrite(c) != C_OK) return;
|
||||
|
|
|
|||
|
|
@ -37,10 +37,13 @@ int keyspaceEventsStringToFlags(char *classes) {
|
|||
case 't': flags |= NOTIFY_STREAM; break;
|
||||
case 'm': flags |= NOTIFY_KEY_MISS; break;
|
||||
case 'd': flags |= NOTIFY_MODULE; break;
|
||||
case 'a': flags |= NOTIFY_ARRAY; break;
|
||||
case 'n': flags |= NOTIFY_NEW; break;
|
||||
case 'o': flags |= NOTIFY_OVERWRITTEN; break;
|
||||
case 'c': flags |= NOTIFY_TYPE_CHANGED; break;
|
||||
#ifdef ENABLE_GCRA
|
||||
case 'r': flags |= NOTIFY_RATE_LIMIT; break;
|
||||
#endif
|
||||
case 'S': flags |= NOTIFY_SUBKEYSPACE; break;
|
||||
case 'T': flags |= NOTIFY_SUBKEYEVENT; break;
|
||||
case 'I': flags |= NOTIFY_SUBKEYSPACEITEM; break;
|
||||
|
|
@ -72,10 +75,13 @@ sds keyspaceEventsFlagsToString(int flags) {
|
|||
if (flags & NOTIFY_EVICTED) res = sdscatlen(res,"e",1);
|
||||
if (flags & NOTIFY_STREAM) res = sdscatlen(res,"t",1);
|
||||
if (flags & NOTIFY_MODULE) res = sdscatlen(res,"d",1);
|
||||
if (flags & NOTIFY_ARRAY) res = sdscatlen(res,"a",1);
|
||||
if (flags & NOTIFY_NEW) res = sdscatlen(res,"n",1);
|
||||
if (flags & NOTIFY_OVERWRITTEN) res = sdscatlen(res,"o",1);
|
||||
if (flags & NOTIFY_TYPE_CHANGED) res = sdscatlen(res,"c",1);
|
||||
#ifdef ENABLE_GCRA
|
||||
if (flags & NOTIFY_RATE_LIMIT) res = sdscatlen(res,"r",1);
|
||||
#endif
|
||||
}
|
||||
if (flags & NOTIFY_KEYSPACE) res = sdscatlen(res,"K",1);
|
||||
if (flags & NOTIFY_KEYEVENT) res = sdscatlen(res,"E",1);
|
||||
|
|
|
|||
46
src/object.c
46
src/object.c
|
|
@ -514,6 +514,7 @@ robj *createStreamObject(void) {
|
|||
return o;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_GCRA
|
||||
robj *createGCRAObject(long long value) {
|
||||
/* NOTE: for 32-bit systems we can't use integer encoding (as OBJ_STRING does)
|
||||
* as the GCRA object is a unixtime value in microseconds, which as of the
|
||||
|
|
@ -530,6 +531,14 @@ robj *createGCRAObject(long long value) {
|
|||
o->encoding = OBJ_ENCODING_INT;
|
||||
return o;
|
||||
}
|
||||
#endif
|
||||
|
||||
robj *createArrayObject(void) {
|
||||
redisArray *ar = arNew();
|
||||
robj *o = createObject(OBJ_ARRAY, ar);
|
||||
o->encoding = OBJ_ENCODING_SLICED_ARRAY;
|
||||
return o;
|
||||
}
|
||||
|
||||
robj *createModuleObject(moduleType *mt, void *value) {
|
||||
moduleValue *mv = zmalloc(sizeof(*mv));
|
||||
|
|
@ -603,6 +612,7 @@ void freeStreamObject(robj *o) {
|
|||
freeStream(o->ptr);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_GCRA
|
||||
void freeGCRAObject(robj *o) {
|
||||
#if UINTPTR_MAX == 0xffffffff
|
||||
zfree(o->ptr);
|
||||
|
|
@ -610,6 +620,11 @@ void freeGCRAObject(robj *o) {
|
|||
(void)o;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
void freeArrayObject(robj *o) {
|
||||
arFree(o->ptr);
|
||||
}
|
||||
|
||||
void incrRefCount(robj *o) {
|
||||
if (o->refcount < OBJ_FIRST_SPECIAL_REFCOUNT - 1) {
|
||||
|
|
@ -662,7 +677,10 @@ void decrRefCount(robj *o) {
|
|||
case OBJ_HASH: freeHashObject(o); break;
|
||||
case OBJ_MODULE: freeModuleObject(o); break;
|
||||
case OBJ_STREAM: freeStreamObject(o); break;
|
||||
#ifdef ENABLE_GCRA
|
||||
case OBJ_GCRA: freeGCRAObject(o); break;
|
||||
#endif
|
||||
case OBJ_ARRAY: freeArrayObject(o); break;
|
||||
default: serverPanic("Unknown object type"); break;
|
||||
}
|
||||
}
|
||||
|
|
@ -810,12 +828,19 @@ void dismissStreamObject(robj *o, size_t size_hint) {
|
|||
}
|
||||
}
|
||||
|
||||
/* See dismissObject() */
|
||||
void dismissArrayObject(robj *o, size_t size_hint) {
|
||||
arDismiss(o->ptr, size_hint);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_GCRA
|
||||
void dismissGCRAObject(robj *o, size_t size_hint) {
|
||||
/* GCRA is a single allocation of a long long thus way smaller than a
|
||||
* page-size. The dismiss mechanism is not needed for it - hence NOOP.*/
|
||||
(void)o;
|
||||
(void)size_hint;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* When creating a snapshot in a fork child process, the main process and child
|
||||
* process share the same physical memory pages, and if / when the parent
|
||||
|
|
@ -845,7 +870,10 @@ void dismissObject(robj *o, size_t size_hint) {
|
|||
case OBJ_ZSET: dismissZsetObject(o, size_hint); break;
|
||||
case OBJ_HASH: dismissHashObject(o, size_hint); break;
|
||||
case OBJ_STREAM: dismissStreamObject(o, size_hint); break;
|
||||
#ifdef ENABLE_GCRA
|
||||
case OBJ_GCRA: dismissGCRAObject(o, size_hint); break;
|
||||
#endif
|
||||
case OBJ_ARRAY: dismissArrayObject(o, size_hint); break;
|
||||
default: break;
|
||||
}
|
||||
#else
|
||||
|
|
@ -967,7 +995,10 @@ size_t getObjectLength(robj *o) {
|
|||
case OBJ_ZSET: return zsetLength(o);
|
||||
case OBJ_HASH: return hashTypeLength(o, 0);
|
||||
case OBJ_STREAM: return streamLength(o);
|
||||
#ifdef ENABLE_GCRA
|
||||
case OBJ_GCRA: return gcraObjectLength(o);
|
||||
#endif
|
||||
case OBJ_ARRAY: return arCount(o->ptr);
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -1176,6 +1207,7 @@ int getLongLongFromObject(robj *o, long long *target) {
|
|||
return C_OK;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_GCRA
|
||||
int getLongLongFromGCRAObject(robj *o, long long *target) {
|
||||
long long res;
|
||||
serverAssertWithInfo(NULL, o, o->type == OBJ_GCRA);
|
||||
|
|
@ -1191,6 +1223,7 @@ int getLongLongFromGCRAObject(robj *o, long long *target) {
|
|||
*target = res;
|
||||
return C_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
int getLongLongFromObjectOrReply(client *c, robj *o, long long *target, const char *msg) {
|
||||
long long value;
|
||||
|
|
@ -1265,6 +1298,7 @@ char *strEncoding(int encoding) {
|
|||
case OBJ_ENCODING_SKIPLIST: return "skiplist";
|
||||
case OBJ_ENCODING_EMBSTR: return "embstr";
|
||||
case OBJ_ENCODING_STREAM: return "stream";
|
||||
case OBJ_ENCODING_SLICED_ARRAY: return "sliced-array";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
|
@ -1283,7 +1317,10 @@ size_t kvobjComputeSize(robj *key, kvobj *o, size_t sample_size, int dbid) {
|
|||
o->type == OBJ_ZSET ||
|
||||
o->type == OBJ_HASH ||
|
||||
o->type == OBJ_STREAM ||
|
||||
o->type == OBJ_GCRA)
|
||||
#ifdef ENABLE_GCRA
|
||||
o->type == OBJ_GCRA ||
|
||||
#endif
|
||||
o->type == OBJ_ARRAY)
|
||||
{
|
||||
return kvobjAllocSize(o);
|
||||
} else if (o->type == OBJ_MODULE) {
|
||||
|
|
@ -1309,14 +1346,20 @@ size_t kvobjAllocSize(kvobj *o) {
|
|||
} else if (o->type == OBJ_STREAM) {
|
||||
stream *s = o->ptr;
|
||||
asize += s->alloc_size;
|
||||
#ifdef ENABLE_GCRA
|
||||
} else if (o->type == OBJ_GCRA) {
|
||||
asize += gcraTypeAllocSize(o);
|
||||
#endif
|
||||
} else if (o->type == OBJ_ARRAY) {
|
||||
redisArray *ar = o->ptr;
|
||||
asize += ar->alloc_size;
|
||||
} else if (o->type == OBJ_MODULE) {
|
||||
/* TODO: Provide moduleGetAllocSize() module API for O(1) allocation size retrieval */
|
||||
}
|
||||
return asize;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_GCRA
|
||||
size_t gcraTypeAllocSize(robj *o) {
|
||||
(void)o;
|
||||
#if UINTPTR_MAX == 0xffffffff
|
||||
|
|
@ -1333,6 +1376,7 @@ size_t gcraObjectLength(robj *o) {
|
|||
(void)o;
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Release data obtained with getMemoryOverheadData(). */
|
||||
void freeMemoryOverheadData(struct redisMemOverhead *mh) {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
* values of different logical types (strings, lists, sets, hashes, sorted sets,
|
||||
* streams, modules, ...). It contains:
|
||||
* - type: one of OBJ_STRING, OBJ_LIST, OBJ_SET, OBJ_ZSET, OBJ_HASH, OBJ_STREAM,
|
||||
* OBJ_GCRA, OBJ_MODULE, ...
|
||||
* OBJ_MODULE, ...
|
||||
* - encoding: an implementation detail of how the value is represented in
|
||||
* memory for the given type (see OBJ_ENCODING_* below). For example,
|
||||
* strings may be RAW/EMBSTR/INT, sets may be INTSET or HT, etc.
|
||||
|
|
@ -85,6 +85,7 @@ struct RedisModuleType;
|
|||
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */
|
||||
#define OBJ_ENCODING_LISTPACK 11 /* Encoded as a listpack */
|
||||
#define OBJ_ENCODING_LISTPACK_EX 12 /* Encoded as listpack, extended with metadata */
|
||||
#define OBJ_ENCODING_SLICED_ARRAY 13 /* Encoded as sliced array */
|
||||
|
||||
#define LRU_BITS 24
|
||||
#define LRU_CLOCK_MAX ((1<<LRU_BITS)-1) /* Max value of obj->lru */
|
||||
|
|
@ -163,6 +164,7 @@ robj *createZsetListpackObject(void);
|
|||
robj *createStreamObject(void);
|
||||
robj *createGCRAObject(long long value);
|
||||
robj *createModuleObject(struct RedisModuleType *mt, void *value);
|
||||
robj *createArrayObject(void);
|
||||
int getLongFromObjectOrReply(struct client *c, robj *o, long *target, const char *msg);
|
||||
int getPositiveLongFromObjectOrReply(struct client *c, robj *o, long *target, const char *msg);
|
||||
int getRangeLongFromObjectOrReply(struct client *c, robj *o, long min, long max, long *target, const char *msg);
|
||||
|
|
|
|||
267
src/rdb.c
267
src/rdb.c
|
|
@ -124,33 +124,42 @@ time_t rdbLoadTime(rio *rdb) {
|
|||
return (time_t)t32;
|
||||
}
|
||||
|
||||
ssize_t rdbSaveMillisecondTime(rio *rdb, long long t) {
|
||||
int64_t t64 = (int64_t) t;
|
||||
memrev64ifbe(&t64); /* Store in little endian. */
|
||||
return rdbWriteRaw(rdb,&t64,8);
|
||||
/* Save a signed 64-bit integer in little-endian format. */
|
||||
ssize_t rdbSaveSignedInteger(rio *rdb, int64_t val) {
|
||||
memrev64ifbe(&val); /* Store in little endian. */
|
||||
return rdbWriteRaw(rdb, &val, 8);
|
||||
}
|
||||
|
||||
/* This function loads a time from the RDB file. It gets the version of the
|
||||
* RDB because, unfortunately, before Redis 5 (RDB version 9), the function
|
||||
* failed to convert data to/from little endian, so RDB files with keys having
|
||||
* expires could not be shared between big endian and little endian systems
|
||||
* (because the expire time will be totally wrong). The fix for this is just
|
||||
* to call memrev64ifbe(), however if we fix this for all the RDB versions,
|
||||
/* This function loads a signed 64-bit integer from the RDB file. It gets the
|
||||
* version of the RDB because, unfortunately, before Redis 5 (RDB version 9),
|
||||
* the function failed to convert data to/from little endian, so RDB files with
|
||||
* keys having expires could not be shared between big endian and little endian
|
||||
* systems (because the expire time will be totally wrong). The fix for this is
|
||||
* just to call memrev64ifbe(), however if we fix this for all the RDB versions,
|
||||
* this call will introduce an incompatibility for big endian systems:
|
||||
* after upgrading to Redis version 5 they will no longer be able to load their
|
||||
* own old RDB files. Because of that, we instead fix the function only for new
|
||||
* RDB versions, and load older RDB versions as we used to do in the past,
|
||||
* allowing big endian systems to load their own old RDB files.
|
||||
*
|
||||
* On I/O error the function returns LLONG_MAX, however if this is also a
|
||||
* On I/O error the function returns INT64_MAX, however if this is also a
|
||||
* valid stored value, the caller should use rioGetReadError() to check for
|
||||
* errors after calling this function. */
|
||||
long long rdbLoadMillisecondTime(rio *rdb, int rdbver) {
|
||||
int64_t t64;
|
||||
if (rioRead(rdb,&t64,8) == 0) return LLONG_MAX;
|
||||
int64_t rdbLoadSignedInteger(rio *rdb, int rdbver) {
|
||||
int64_t val;
|
||||
if (rioRead(rdb, &val, 8) == 0) return INT64_MAX;
|
||||
if (rdbver >= 9) /* Check the top comment of this function. */
|
||||
memrev64ifbe(&t64); /* Convert in big endian if the system is BE. */
|
||||
return (long long)t64;
|
||||
memrev64ifbe(&val); /* Convert in big endian if the system is BE. */
|
||||
return val;
|
||||
}
|
||||
|
||||
/* Wrappers for millisecond time - these just call the signed integer functions */
|
||||
ssize_t rdbSaveMillisecondTime(rio *rdb, long long t) {
|
||||
return rdbSaveSignedInteger(rdb, (int64_t)t);
|
||||
}
|
||||
|
||||
long long rdbLoadMillisecondTime(rio *rdb, int rdbver) {
|
||||
return (long long)rdbLoadSignedInteger(rdb, rdbver);
|
||||
}
|
||||
|
||||
/* Saves an encoded length. The first two bits in the first byte are used to
|
||||
|
|
@ -714,10 +723,14 @@ int rdbSaveObjectType(rio *rdb, robj *o) {
|
|||
serverPanic("Unknown hash encoding");
|
||||
case OBJ_STREAM:
|
||||
return rdbSaveType(rdb,RDB_TYPE_STREAM_LISTPACKS_5);
|
||||
#ifdef ENABLE_GCRA
|
||||
case OBJ_GCRA:
|
||||
return rdbSaveType(rdb,RDB_TYPE_GCRA);
|
||||
#endif
|
||||
case OBJ_MODULE:
|
||||
return rdbSaveType(rdb,RDB_TYPE_MODULE_2);
|
||||
case OBJ_ARRAY:
|
||||
return rdbSaveType(rdb,RDB_TYPE_ARRAY);
|
||||
default:
|
||||
serverPanic("Unknown object type");
|
||||
}
|
||||
|
|
@ -1040,6 +1053,68 @@ size_t rdbSaveStreamConsumers(rio *rdb, streamCG *cg) {
|
|||
|
||||
/* Save a Redis object.
|
||||
* Returns -1 on error, number of bytes written on success. */
|
||||
static ssize_t rdbSaveArrayElement(rio *rdb, uint64_t idx, void *v) {
|
||||
ssize_t n, nwritten = 0;
|
||||
|
||||
if ((n = rdbSaveLen(rdb, idx)) == -1) return -1;
|
||||
nwritten += n;
|
||||
|
||||
if (arIsInt(v)) {
|
||||
if ((n = rdbSaveLen(rdb, AR_RDB_TAG_INT)) == -1) return -1;
|
||||
nwritten += n;
|
||||
int64_t ival = arToInt(v);
|
||||
if ((n = rdbSaveSignedInteger(rdb, ival)) == -1) return -1;
|
||||
nwritten += n;
|
||||
} else if (arIsFloat(v)) {
|
||||
if ((n = rdbSaveLen(rdb, AR_RDB_TAG_FLOAT)) == -1) return -1;
|
||||
nwritten += n;
|
||||
double d = arToDouble(v);
|
||||
if (rdbSaveBinaryDoubleValue(rdb, d) == -1) return -1;
|
||||
nwritten += 8;
|
||||
} else if (arIsSmallStr(v)) {
|
||||
char buf[AR_SMALLSTR_MAXLEN + 1];
|
||||
int len = arToSmallStr(v, buf);
|
||||
if ((n = rdbSaveLen(rdb, AR_RDB_TAG_SMALLSTR)) == -1) return -1;
|
||||
nwritten += n;
|
||||
if ((n = rdbSaveRawString(rdb, (unsigned char *)buf, len)) == -1) return -1;
|
||||
nwritten += n;
|
||||
} else {
|
||||
if ((n = rdbSaveLen(rdb, AR_RDB_TAG_SDS)) == -1) return -1;
|
||||
nwritten += n;
|
||||
if ((n = rdbSaveRawString(rdb, (unsigned char *)arStringData(v), arStringLen(v))) == -1) return -1;
|
||||
nwritten += n;
|
||||
}
|
||||
|
||||
return nwritten;
|
||||
}
|
||||
|
||||
static ssize_t rdbSaveArraySlice(rio *rdb, arSlice *s, uint64_t slice_id,
|
||||
uint32_t slice_size) {
|
||||
ssize_t n, nwritten = 0;
|
||||
|
||||
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 ((n = rdbSaveArrayElement(rdb, idx, v)) == -1) return -1;
|
||||
nwritten += n;
|
||||
}
|
||||
} else {
|
||||
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 ((n = rdbSaveArrayElement(rdb, idx, values[i])) == -1) return -1;
|
||||
nwritten += n;
|
||||
}
|
||||
}
|
||||
|
||||
return nwritten;
|
||||
}
|
||||
|
||||
ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key, int dbid) {
|
||||
ssize_t n = 0, nwritten = 0;
|
||||
|
||||
|
|
@ -1402,11 +1477,13 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key, int dbid) {
|
|||
/* Save the all-time count of duplicate IIDs detected. */
|
||||
if ((n = rdbSaveLen(rdb,s->iids_duplicates)) == -1) return -1;
|
||||
nwritten += n;
|
||||
#ifdef ENABLE_GCRA
|
||||
} else if (o->type == OBJ_GCRA) {
|
||||
long long t;
|
||||
getLongLongFromGCRAObject(o, &t);
|
||||
if ((n = rdbSaveLen(rdb,t)) == -1) return -1;
|
||||
nwritten += n;
|
||||
#endif
|
||||
} else if (o->type == OBJ_MODULE) {
|
||||
/* Save a module-specific value. */
|
||||
RedisModuleIO io;
|
||||
|
|
@ -1433,6 +1510,57 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key, int dbid) {
|
|||
zfree(io.ctx);
|
||||
}
|
||||
return io.error ? -1 : (ssize_t)io.bytes;
|
||||
} else if (o->type == OBJ_ARRAY) {
|
||||
/* Save an array value. We persist only elements and insert_idx - no
|
||||
* implementation details like slice_size. Arrays are loaded using
|
||||
* the current ar_slice_size config. */
|
||||
redisArray *ar = o->ptr;
|
||||
|
||||
/* Save count */
|
||||
if ((n = rdbSaveLen(rdb, ar->count)) == -1) return -1;
|
||||
nwritten += n;
|
||||
|
||||
/* Save insert_idx: 0 = none, 1 = has value followed by actual value.
|
||||
* We can't save UINT64_MAX directly with rdbSaveLen/rdbLoadLen because
|
||||
* rdbLoadLen returns UINT64_MAX (RDB_LENERR) to signal an error, making
|
||||
* it impossible to distinguish a valid UINT64_MAX value from an error. */
|
||||
if (ar->insert_idx == AR_INSERT_IDX_NONE) {
|
||||
if ((n = rdbSaveLen(rdb, 0)) == -1) return -1;
|
||||
nwritten += n;
|
||||
} else {
|
||||
if ((n = rdbSaveLen(rdb, 1)) == -1) return -1;
|
||||
nwritten += n;
|
||||
if ((n = rdbSaveLen(rdb, ar->insert_idx)) == -1) return -1;
|
||||
nwritten += n;
|
||||
}
|
||||
|
||||
/* Save elements in index order.
|
||||
* We need to iterate through all slices, handling both flat directory
|
||||
* mode and superdir mode. In superdir mode, blocks are sorted by
|
||||
* block_id, so we iterate through blocks in order. */
|
||||
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 ((n = rdbSaveArraySlice(rdb, s, slice_id, ar->slice_size)) == -1) return -1;
|
||||
nwritten += n;
|
||||
}
|
||||
}
|
||||
} 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 ((n = rdbSaveArraySlice(rdb, s, slice_id, ar->slice_size)) == -1) return -1;
|
||||
nwritten += n;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
serverPanic("Unknown object type");
|
||||
}
|
||||
|
|
@ -2935,13 +3063,13 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error)
|
|||
|
||||
/* search for duplicate records */
|
||||
sds field = sdstrynewlen(fstr, flen);
|
||||
int field_added = (field != NULL && dictAdd(dupSearchDict, field, NULL) == DICT_OK);
|
||||
if (!field_added || !lpSafeToAdd(lp, (size_t)flen + vlen)) {
|
||||
if (!field || !lpSafeToAdd(lp, (size_t)flen + vlen) ||
|
||||
dictAdd(dupSearchDict, field, NULL) != DICT_OK) {
|
||||
rdbReportCorruptRDB("Hash zipmap with dup elements, or big length (%u)", flen);
|
||||
/* If field was not added to dict, we still own it.
|
||||
* If it was added, dict owns it and dictRelease will free it. */
|
||||
if (!field_added) sdsfree(field);
|
||||
dictRelease(dupSearchDict);
|
||||
sdsfree(field);
|
||||
lpFree(lp);
|
||||
zfree(encoded);
|
||||
o->ptr = NULL;
|
||||
|
|
@ -3550,7 +3678,6 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error)
|
|||
rdbReportCorruptRDB("Duplicated consumer PEL entry "
|
||||
" loading a stream consumer "
|
||||
"group");
|
||||
streamFreeNACK(s, nack);
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -3736,6 +3863,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error)
|
|||
return NULL;
|
||||
}
|
||||
o = createModuleObject(mt, ptr);
|
||||
#ifdef ENABLE_GCRA
|
||||
} else if (rdbtype == RDB_TYPE_GCRA) {
|
||||
uint64_t time = rdbLoadLen(rdb, NULL);
|
||||
if (time == RDB_LENERR || time > LLONG_MAX) {
|
||||
|
|
@ -3743,6 +3871,105 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error)
|
|||
return NULL;
|
||||
}
|
||||
o = createGCRAObject((long long)time);
|
||||
#endif
|
||||
} else if (rdbtype == RDB_TYPE_ARRAY) {
|
||||
/* Load array value. We only persist elements and insert_idx - no
|
||||
* implementation details. Arrays use current ar_slice_size config. */
|
||||
uint64_t count;
|
||||
if ((count = rdbLoadLen(rdb, NULL)) == RDB_LENERR) return NULL;
|
||||
if (count == 0) {
|
||||
rdbReportCorruptRDB("Empty array (count == 0) is invalid");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Load insert_idx: 0 = none, 1 = has value followed by actual value */
|
||||
uint64_t insert_idx_flag;
|
||||
if ((insert_idx_flag = rdbLoadLen(rdb, NULL)) == RDB_LENERR) return NULL;
|
||||
if (insert_idx_flag > 1) {
|
||||
rdbReportCorruptRDB("Invalid array insert_idx_flag %llu",
|
||||
(unsigned long long)insert_idx_flag);
|
||||
return NULL;
|
||||
}
|
||||
uint64_t insert_idx;
|
||||
if (insert_idx_flag == 0) {
|
||||
insert_idx = AR_INSERT_IDX_NONE;
|
||||
} else {
|
||||
if ((insert_idx = rdbLoadLen(rdb, NULL)) == RDB_LENERR) return NULL;
|
||||
}
|
||||
|
||||
o = createArrayObject();
|
||||
redisArray *ar = o->ptr;
|
||||
ar->insert_idx = insert_idx;
|
||||
|
||||
/* Load elements */
|
||||
for (uint64_t i = 0; i < count; i++) {
|
||||
uint64_t idx;
|
||||
int idx_isencoded;
|
||||
if (rdbLoadLenByRef(rdb, &idx_isencoded, &idx) == -1) {
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
if (idx_isencoded || idx == UINT64_MAX) {
|
||||
decrRefCount(o);
|
||||
rdbReportCorruptRDB("Invalid array index %llu",
|
||||
(unsigned long long)idx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint64_t type_tag;
|
||||
if ((type_tag = rdbLoadLen(rdb, NULL)) == RDB_LENERR) {
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void *v;
|
||||
if (type_tag == AR_RDB_TAG_INT) {
|
||||
int64_t ival = rdbLoadSignedInteger(rdb, RDB_VERSION);
|
||||
if (ival == INT64_MAX && rioGetReadError(rdb)) {
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
v = arValueFromRdbInt(ival);
|
||||
} else if (type_tag == AR_RDB_TAG_FLOAT) {
|
||||
double d;
|
||||
if (rdbLoadBinaryDoubleValue(rdb, &d) == -1) {
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
v = arValueFromRdbFloat(d);
|
||||
} else if (type_tag == AR_RDB_TAG_SMALLSTR) {
|
||||
sds str;
|
||||
if ((str = rdbGenericLoadStringObject(rdb, RDB_LOAD_SDS, NULL)) == NULL) {
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
size_t len = sdslen(str);
|
||||
if (len > AR_SMALLSTR_MAXLEN) {
|
||||
sdsfree(str);
|
||||
decrRefCount(o);
|
||||
rdbReportCorruptRDB("Invalid small string length %zu in array", len);
|
||||
return NULL;
|
||||
}
|
||||
v = arValueFromRdbSmallStr(str, sdslen(str));
|
||||
sdsfree(str);
|
||||
} else if (type_tag == AR_RDB_TAG_SDS) {
|
||||
/* arString */
|
||||
sds str;
|
||||
if ((str = rdbGenericLoadStringObject(rdb, RDB_LOAD_SDS, NULL)) == NULL) {
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
v = arEncode(str, sdslen(str));
|
||||
sdsfree(str);
|
||||
} else {
|
||||
decrRefCount(o);
|
||||
rdbReportCorruptRDB("Unknown array element type_tag %llu",
|
||||
(unsigned long long)type_tag);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
arSet(ar, idx, v);
|
||||
}
|
||||
} else {
|
||||
rdbReportReadError("Unknown RDB encoding type %d",rdbtype);
|
||||
return NULL;
|
||||
|
|
|
|||
11
src/rdb.h
11
src/rdb.h
|
|
@ -80,11 +80,18 @@
|
|||
#define RDB_TYPE_HASH_LISTPACK_EX 25 /* Hash LP with HFEs. Attach min TTL at start */
|
||||
#define RDB_TYPE_STREAM_LISTPACKS_4 26 /* Stream with IDMP support */
|
||||
#define RDB_TYPE_STREAM_LISTPACKS_5 27 /* Stream with XNACK support (NACKed entries) */
|
||||
#define RDB_TYPE_GCRA 28 /* GCRA object */
|
||||
#define RDB_TYPE_ARRAY 28 /* Array data type */
|
||||
#ifdef ENABLE_GCRA
|
||||
#define RDB_TYPE_GCRA 29 /* GCRA object */
|
||||
#endif
|
||||
/* NOTE: WHEN ADDING NEW RDB TYPE, UPDATE rdbIsObjectType(), and rdb_type_string[] */
|
||||
|
||||
/* Test if a type is an object type. */
|
||||
#ifdef ENABLE_GCRA
|
||||
#define rdbIsObjectType(t) (((t) >= 0 && (t) <= 7) || ((t) >= 9 && (t) <= 29))
|
||||
#else
|
||||
#define rdbIsObjectType(t) (((t) >= 0 && (t) <= 7) || ((t) >= 9 && (t) <= 28))
|
||||
#endif
|
||||
|
||||
/* Special RDB opcodes (saved/loaded with rdbSaveType/rdbLoadType). */
|
||||
#define RDB_OPCODE_KEY_META 243 /* Key metadata (module metadata classes). */
|
||||
|
|
@ -133,6 +140,8 @@ int rdbSaveType(rio *rdb, unsigned char type);
|
|||
int rdbLoadType(rio *rdb);
|
||||
time_t rdbLoadTime(rio *rdb);
|
||||
int rdbSaveLen(rio *rdb, uint64_t len);
|
||||
ssize_t rdbSaveSignedInteger(rio *rdb, int64_t val);
|
||||
int64_t rdbLoadSignedInteger(rio *rdb, int rdbver);
|
||||
ssize_t rdbSaveMillisecondTime(rio *rdb, long long t);
|
||||
long long rdbLoadMillisecondTime(rio *rdb, int rdbver);
|
||||
uint64_t rdbLoadLen(rio *rdb, int *isencoded);
|
||||
|
|
|
|||
|
|
@ -88,7 +88,10 @@ char *rdb_type_string[] = {
|
|||
"hash-listpack-md",
|
||||
"stream-v4",
|
||||
"stream-v5",
|
||||
"array",
|
||||
#ifdef ENABLE_GCRA
|
||||
"gcra",
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Show a few stats collected into 'rdbstate' */
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ typedef long long ustime_t;
|
|||
#define REDISMODULE_KEYTYPE_ZSET 5
|
||||
#define REDISMODULE_KEYTYPE_MODULE 6
|
||||
#define REDISMODULE_KEYTYPE_STREAM 7
|
||||
#define REDISMODULE_KEYTYPE_GCRA 8
|
||||
#define REDISMODULE_KEYTYPE_ARRAY 8
|
||||
|
||||
/* Reply types. */
|
||||
#define REDISMODULE_REPLY_UNKNOWN -1
|
||||
|
|
@ -248,24 +248,31 @@ This flag should not be used directly by the module.
|
|||
#define REDISMODULE_NOTIFY_OVERWRITTEN (1<<15) /* o, key overwrite notification */
|
||||
#define REDISMODULE_NOTIFY_TYPE_CHANGED (1<<16) /* c, key type changed notification */
|
||||
#define REDISMODULE_NOTIFY_KEY_TRIMMED (1<<17) /* module only key space notification, indicates a key trimmed during slot migration */
|
||||
#define REDISMODULE_NOTIFY_RATE_LIMIT (1<<18) /* r, rate limit event */
|
||||
|
||||
#define REDISMODULE_NOTIFY_SUBKEYSPACE (1<<19) /* S */
|
||||
#define REDISMODULE_NOTIFY_SUBKEYEVENT (1<<20) /* T */
|
||||
#define REDISMODULE_NOTIFY_SUBKEYSPACEITEM (1<<21) /* I */
|
||||
#define REDISMODULE_NOTIFY_SUBKEYSPACEEVENT (1<<22) /* V */
|
||||
#define REDISMODULE_NOTIFY_ARRAY (1<<23) /* a, array key space notification */
|
||||
#ifdef ENABLE_GCRA
|
||||
#define REDISMODULE_NOTIFY_RATE_LIMIT (1<<24) /* r, rate limit event */
|
||||
#endif
|
||||
|
||||
/* Next notification flag, must be updated when adding new flags above!
|
||||
This flag should not be used directly by the module.
|
||||
* Use RedisModule_GetKeyspaceNotificationFlagsAll instead. */
|
||||
#define _REDISMODULE_NOTIFY_NEXT (1<<23)
|
||||
#ifdef ENABLE_GCRA
|
||||
#define _REDISMODULE_NOTIFY_NEXT (1<<25)
|
||||
#else
|
||||
#define _REDISMODULE_NOTIFY_NEXT (1<<24)
|
||||
#endif
|
||||
|
||||
/* Delivery flags for RM_SubscribeToKeyspaceEventsWithSubkeys.
|
||||
* These are passed in the 'flags' parameter, not in 'types'. */
|
||||
#define REDISMODULE_NOTIFY_FLAG_NONE 0 /* Invoke callback for all matching events */
|
||||
#define REDISMODULE_NOTIFY_FLAG_SUBKEYS_REQUIRED (1<<0) /* Only invoke callback when subkeys are present */
|
||||
|
||||
#define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED | REDISMODULE_NOTIFY_STREAM | REDISMODULE_NOTIFY_MODULE) /* A */
|
||||
#define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED | REDISMODULE_NOTIFY_STREAM | REDISMODULE_NOTIFY_MODULE | REDISMODULE_NOTIFY_ARRAY) /* A */
|
||||
|
||||
/* A special pointer that we can use between the core and the module to signal
|
||||
* field deletion, and that is impossible to be a valid pointer. */
|
||||
|
|
|
|||
|
|
@ -2251,6 +2251,11 @@ void replicationAttachToNewMaster(void) {
|
|||
/* Asynchronously read the SYNC payload we receive from a master */
|
||||
#define REPL_MAX_WRITTEN_BEFORE_FSYNC (1024*1024*8) /* 8 MB */
|
||||
void readSyncBulkPayload(connection *conn) {
|
||||
/* During full sync, the functions engine is freed right before loading
|
||||
* the RDB. To avoid this happening while a function is still running,
|
||||
* delay full sync processing until it finishes. */
|
||||
if (isInsideYieldingLongCommand()) return;
|
||||
|
||||
char buf[PROTO_IOBUF_LEN];
|
||||
ssize_t nread, readlen, nwritten;
|
||||
int use_diskless_load = useDisklessLoad();
|
||||
|
|
|
|||
|
|
@ -105,7 +105,14 @@ sds _sdsnewlen(const void *init, size_t initlen, int trymalloc) {
|
|||
int hdrlen = sdsHdrSize(type);
|
||||
size_t bufsize;
|
||||
|
||||
assert(initlen + hdrlen + 1 > initlen); /* Catch size_t overflow */
|
||||
if (trymalloc) {
|
||||
/* protect against size_t overflow */
|
||||
if (initlen + hdrlen + 1 <= initlen)
|
||||
return NULL;
|
||||
} else {
|
||||
assert(initlen + hdrlen + 1 > initlen); /* Catch size_t overflow */
|
||||
}
|
||||
|
||||
sh = trymalloc?
|
||||
s_trymalloc_usable(hdrlen+initlen+1, &bufsize) :
|
||||
s_malloc_usable(hdrlen+initlen+1, &bufsize);
|
||||
|
|
|
|||
54
src/server.h
54
src/server.h
|
|
@ -22,6 +22,7 @@
|
|||
#include "atomicvar.h"
|
||||
#include "commands.h"
|
||||
#include "object.h"
|
||||
#include "sparsearray.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
|
@ -287,7 +288,10 @@ extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT];
|
|||
#define ACL_CATEGORY_CONNECTION (1ULL<<18)
|
||||
#define ACL_CATEGORY_TRANSACTION (1ULL<<19)
|
||||
#define ACL_CATEGORY_SCRIPTING (1ULL<<20)
|
||||
#define ACL_CATEGORY_RATE_LIMIT (1ULL<<21)
|
||||
#define ACL_CATEGORY_ARRAY (1ULL<<21)
|
||||
#ifdef ENABLE_GCRA
|
||||
#define ACL_CATEGORY_RATE_LIMIT (1ULL<<22)
|
||||
#endif
|
||||
|
||||
/* Key-spec flags *
|
||||
* -------------- */
|
||||
|
|
@ -796,12 +800,15 @@ typedef enum {
|
|||
#define NOTIFY_OVERWRITTEN (1<<15) /* o, key overwrite notification (Note: excluded from NOTIFY_ALL) */
|
||||
#define NOTIFY_TYPE_CHANGED (1<<16) /* c, key type changed notification (Note: excluded from NOTIFY_ALL) */
|
||||
#define NOTIFY_KEY_TRIMMED (1<<17) /* module only key space notification, indicates a key trimmed during slot migration */
|
||||
#define NOTIFY_RATE_LIMIT (1<<18) /* r, notify rate limit event (Note: excluded from NOTIFY_ALL)*/
|
||||
#define NOTIFY_SUBKEYSPACE (1<<19) /* S, subkey-level keyspace notification */
|
||||
#define NOTIFY_SUBKEYEVENT (1<<20) /* T, subkey-level keyevent notification */
|
||||
#define NOTIFY_SUBKEYSPACEITEM (1<<21) /* I, subkey-level notification per item: channel=key\nsubkey */
|
||||
#define NOTIFY_SUBKEYSPACEEVENT (1<<22) /* V, subkey-level notification: channel=event|key */
|
||||
#define NOTIFY_ALL (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_EXPIRED | NOTIFY_EVICTED | NOTIFY_STREAM | NOTIFY_MODULE) /* A flag */
|
||||
#define NOTIFY_ARRAY (1<<23) /* a, array notification */
|
||||
#ifdef ENABLE_GCRA
|
||||
#define NOTIFY_RATE_LIMIT (1<<24) /* r, notify rate limit event (Note: excluded from NOTIFY_ALL)*/
|
||||
#endif
|
||||
#define NOTIFY_ALL (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_EXPIRED | NOTIFY_EVICTED | NOTIFY_STREAM | NOTIFY_MODULE | NOTIFY_ARRAY) /* A flag */
|
||||
|
||||
/* Using the following macro you can run code inside serverCron() with the
|
||||
* specified period, specified in milliseconds.
|
||||
|
|
@ -863,10 +870,18 @@ typedef enum {
|
|||
* by a 64 bit module type ID, which has a 54 bits module-specific signature
|
||||
* in order to dispatch the loading to the right module, plus a 10 bits
|
||||
* encoding version. */
|
||||
/* Code related to GCRA is disabled by default.
|
||||
* Build with -DENABLE_GCRA to compile it back in. */
|
||||
|
||||
#define OBJ_MODULE 5 /* Module object. */
|
||||
#define OBJ_STREAM 6 /* Stream object. */
|
||||
#define OBJ_GCRA 7 /* GCRA object. */
|
||||
#define OBJ_ARRAY 7 /* Array object. */
|
||||
#ifdef ENABLE_GCRA
|
||||
#define OBJ_GCRA 8 /* GCRA object. */
|
||||
#define OBJ_TYPE_MAX 9 /* Maximum number of object types */
|
||||
#else
|
||||
#define OBJ_TYPE_MAX 8 /* Maximum number of object types */
|
||||
#endif
|
||||
|
||||
/* NOTE: adding a new object requires changes in the following places:
|
||||
* - rdb.c - save/load (also bump RDB_VERSION if needed)
|
||||
|
|
@ -2442,6 +2457,10 @@ struct redisServer {
|
|||
/* Stream IDMP parameters */
|
||||
long long stream_idmp_duration; /* Default IDMP duration in seconds. */
|
||||
long long stream_idmp_maxsize; /* Default IDMP max entries. */
|
||||
/* Array parameters */
|
||||
uint32_t array_slice_size; /* Slice size for new arrays */
|
||||
uint32_t array_sparse_kmax; /* Max elements before sparse->dense */
|
||||
uint32_t array_sparse_kmin; /* Min elements before dense->sparse */
|
||||
/* List parameters */
|
||||
int list_max_listpack_size;
|
||||
int list_compress_depth;
|
||||
|
|
@ -2801,8 +2820,11 @@ typedef enum {
|
|||
COMMAND_GROUP_GEO,
|
||||
COMMAND_GROUP_STREAM,
|
||||
COMMAND_GROUP_BITMAP,
|
||||
COMMAND_GROUP_ARRAY,
|
||||
COMMAND_GROUP_MODULE,
|
||||
#ifdef ENABLE_GCRA
|
||||
COMMAND_GROUP_RATE_LIMIT,
|
||||
#endif
|
||||
} redisCommandGroup;
|
||||
|
||||
typedef void redisCommandProc(client *c);
|
||||
|
|
@ -3213,6 +3235,7 @@ void addReplyBigNum(client *c, const char *num, size_t len);
|
|||
void addReplyHumanLongDouble(client *c, long double d);
|
||||
void addReplyLongLong(client *c, long long ll);
|
||||
void addReplyLongLongFromStr(client *c, robj* str);
|
||||
void addReplyUnsignedLongLong(client *c, uint64_t v);
|
||||
void addReplyArrayLen(client *c, long length);
|
||||
void addReplyMapLen(client *c, long length);
|
||||
void addReplySetLen(client *c, long length);
|
||||
|
|
@ -3844,6 +3867,9 @@ struct listpackEx *listpackExCreate(void);
|
|||
void listpackExAddNew(robj *o, char *field, size_t flen,
|
||||
char *value, size_t vlen, uint64_t expireAt);
|
||||
|
||||
/* Array data type. */
|
||||
robj *arrayTypeDup(robj *o);
|
||||
|
||||
/* Pub / Sub */
|
||||
int pubsubUnsubscribeAllChannels(client *c, int notify);
|
||||
int pubsubUnsubscribeShardAllChannels(client *c, int notify);
|
||||
|
|
@ -4511,6 +4537,26 @@ void digestCommand(client *c);
|
|||
void gcraCommand(client *c);
|
||||
void gcraSetValueCommand(client *c);
|
||||
|
||||
/* Array commands (t_array.c) */
|
||||
void arsetCommand(client *c);
|
||||
void argetCommand(client *c);
|
||||
void ardelCommand(client *c);
|
||||
void ardelrangeCommand(client *c);
|
||||
void arlenCommand(client *c);
|
||||
void arcountCommand(client *c);
|
||||
void argetrangeCommand(client *c);
|
||||
void arscanCommand(client *c);
|
||||
void argrepCommand(client *c);
|
||||
void aropCommand(client *c);
|
||||
void arinsertCommand(client *c);
|
||||
void arringCommand(client *c);
|
||||
void arnextCommand(client *c);
|
||||
void arseekCommand(client *c);
|
||||
void arlastitemsCommand(client *c);
|
||||
void arinfoCommand(client *c);
|
||||
void armsetCommand(client *c);
|
||||
void armgetCommand(client *c);
|
||||
|
||||
#if defined(__GNUC__)
|
||||
void *calloc(size_t count, size_t size) __attribute__ ((deprecated));
|
||||
void free(void *ptr) __attribute__ ((deprecated));
|
||||
|
|
|
|||
2080
src/sparsearray.c
Normal file
2080
src/sparsearray.c
Normal file
File diff suppressed because it is too large
Load diff
312
src/sparsearray.h
Normal file
312
src/sparsearray.h
Normal file
|
|
@ -0,0 +1,312 @@
|
|||
/*
|
||||
* Copyright (c) 2026-Present, Redis Ltd.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of (a) the Redis Source Available License 2.0
|
||||
* (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the
|
||||
* GNU Affero General Public License v3 (AGPLv3).
|
||||
*
|
||||
* Sparse Array - A memory-efficient sparse array with 64-bit index space.
|
||||
*
|
||||
* This data structure was designed and implemented by Salvatore Sanfilippo.
|
||||
*/
|
||||
|
||||
#ifndef __SPARSEARRAY_H
|
||||
#define __SPARSEARRAY_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ============================================================================
|
||||
* SPARSE ARRAY OVERVIEW
|
||||
* ============================================================================
|
||||
*
|
||||
* Sparse arrays are random-access sequences indexed by non-negative 64-bit
|
||||
* integers. They support O(1) get/set operations and efficient iteration.
|
||||
*
|
||||
* MEMORY LAYOUT
|
||||
* -------------
|
||||
* The array uses a two-level structure: a directory pointing to "slices",
|
||||
* which contain just a range of elements. For very large/sparse arrays, a
|
||||
* three-level "superdir" structure is used.
|
||||
*
|
||||
* SLICE TYPES
|
||||
* -----------
|
||||
* Each slice holds up to slice_size elements and can be:
|
||||
*
|
||||
* - Sparse: Sorted array of (offset, value) pairs. Memory-efficient when
|
||||
* elements are scattered within the slice.
|
||||
*
|
||||
* - Dense: Contiguous array with a sliding window. Used when the slice
|
||||
* has many elements.
|
||||
*
|
||||
* VALUE ENCODING (Tagged Pointers)
|
||||
* --------------------------------
|
||||
* Values are stored in tagged pointer-sized words, using the low 2 bits as a
|
||||
* tag. The exact immediate encoding depends on pointer width:
|
||||
*
|
||||
* 64-bit builds:
|
||||
* Tag 00: arString pointer (heap-allocated, 8+ byte strings)
|
||||
* Tag 01: Immediate signed integer in the 62-bit payload
|
||||
* Tag 10: Immediate double (low 2 bits of the IEEE-754 payload cleared)
|
||||
* Tag 11: Inline small string (0-7 bytes)
|
||||
*
|
||||
* 32-bit builds:
|
||||
* Tag 00: arString pointer
|
||||
* Tag 01: Immediate signed integer in the 30-bit payload
|
||||
* Tag 10: Immediate float (low 2 bits of the IEEE-754 payload cleared)
|
||||
* Tag 11: Inline small string (0-3 bytes)
|
||||
*
|
||||
* RDB persistence is architecture-neutral: values are saved as logical ints,
|
||||
* doubles and strings, never as raw tagged words.
|
||||
* ========================================================================== */
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Configuration defaults
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
#define AR_SLICE_SIZE_DEFAULT 4096
|
||||
#define AR_SLICE_SIZE_MIN 256
|
||||
#define AR_SLICE_SIZE_MAX 65536
|
||||
#define AR_SPARSE_KMAX_DEFAULT 10
|
||||
#define AR_SPARSE_KMIN_DEFAULT 5
|
||||
|
||||
/* Superdir: fixed-size blocks of slice pointers. Each block holds 2048
|
||||
* pointers to actual array slices, which uses about 8 KB on 32-bit builds
|
||||
* and 16 KB on 64-bit builds. This keeps very large indices from forcing
|
||||
* catastrophic flat-directory growth. */
|
||||
#define AR_SUPER_BLOCK_SLOTS 2048
|
||||
|
||||
/* Internal constants */
|
||||
#define AR_SLICE_MIN_ALLOC 8 /* Initial dense window allocation */
|
||||
#define AR_INSERT_IDX_NONE UINT64_MAX /* No insert performed yet */
|
||||
|
||||
/* Slice encoding types */
|
||||
#define AR_SLICE_DENSE 0
|
||||
#define AR_SLICE_SPARSE 1
|
||||
|
||||
/* Tagged value encoding (low 2 bits). NULL (0) means empty slot. */
|
||||
#define AR_TAG_PTR ((uintptr_t)0) /* arString pointer (low 2 bits = 00) */
|
||||
#define AR_TAG_INT ((uintptr_t)1) /* Immediate signed integer (01) */
|
||||
#define AR_TAG_FLOAT ((uintptr_t)2) /* Immediate float (10) */
|
||||
#define AR_TAG_STR ((uintptr_t)3) /* Inline small string (11) */
|
||||
#define AR_TAG_MASK ((uintptr_t)3)
|
||||
|
||||
#if UINTPTR_MAX == UINT64_MAX
|
||||
#define AR_SMALLSTR_MAXLEN 7
|
||||
#define AR_SMALLSTR_LEN_MASK 0x7u
|
||||
#elif UINTPTR_MAX == UINT32_MAX
|
||||
#define AR_SMALLSTR_MAXLEN 3
|
||||
#define AR_SMALLSTR_LEN_MASK 0x3u
|
||||
#else
|
||||
#error "Unsupported pointer size"
|
||||
#endif
|
||||
|
||||
/* RDB type tags for array elements */
|
||||
#define AR_RDB_TAG_SDS 0
|
||||
#define AR_RDB_TAG_INT 1
|
||||
#define AR_RDB_TAG_FLOAT 2
|
||||
#define AR_RDB_TAG_SMALLSTR 3
|
||||
|
||||
/* Buffer size for inline types (int/float/smallstr) */
|
||||
#define AR_INLINE_BUFSIZE 64
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Data structures
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/* Array slice: holds a range of elements. Single allocation with payload. */
|
||||
typedef struct arSlice {
|
||||
uint8_t encoding; /* 0=dense, 1=sparse */
|
||||
uint8_t _pad1[3];
|
||||
uint32_t count; /* Non-empty items in this slice */
|
||||
union {
|
||||
struct {
|
||||
uint32_t offset; /* First logical offset in window */
|
||||
uint32_t winsize; /* Window size (power of two) */
|
||||
uint32_t max_idx; /* Highest offset with a value */
|
||||
void **items; /* Points into payload */
|
||||
} dense;
|
||||
struct {
|
||||
uint32_t cap; /* Capacity */
|
||||
uint16_t *offsets; /* Points into payload */
|
||||
void **values; /* Points into payload (aligned) */
|
||||
} sparse;
|
||||
} layout;
|
||||
} arSlice;
|
||||
|
||||
/* Super-directory entry: groups slices into fixed-size pointer blocks. */
|
||||
typedef struct arSDirEntry {
|
||||
uint64_t block_id; /* slice_id / AR_SUPER_BLOCK_SLOTS */
|
||||
uint32_t count; /* Non-NULL slots in this block */
|
||||
uint32_t _pad;
|
||||
arSlice **slots; /* AR_SUPER_BLOCK_SLOTS pointers to slices */
|
||||
} arSDirEntry;
|
||||
|
||||
/* Array header */
|
||||
typedef struct redisArray {
|
||||
uint64_t count; /* Total non-empty items */
|
||||
uint64_t insert_idx; /* Last insert index, or UINT64_MAX if none */
|
||||
uint64_t dir_alloc; /* Flat directory length (flat mode) */
|
||||
uint64_t dir_highest_used; /* Highest non-NULL slice index */
|
||||
uint64_t num_slices; /* Number of allocated slices */
|
||||
size_t alloc_size; /* Tracked total allocation (for slot stats) */
|
||||
uint32_t slice_size; /* Slice size (power of two) */
|
||||
uint32_t sdir_len; /* Superdir entries count */
|
||||
uint32_t sdir_cap; /* Superdir capacity */
|
||||
uint32_t _pad;
|
||||
arSlice **dir; /* Flat directory or NULL */
|
||||
arSDirEntry *superdir; /* Super-directory or NULL */
|
||||
} redisArray;
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Inline helpers: index arithmetic
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/* Compute bits needed to address elements within a slice. */
|
||||
static inline int arSliceBits(uint32_t slice_size) {
|
||||
if (slice_size == 4096) return 12; /* Fast path for default */
|
||||
int bits = 0;
|
||||
uint32_t x = slice_size;
|
||||
while (x > 1) { x >>= 1; bits++; }
|
||||
return bits;
|
||||
}
|
||||
|
||||
static inline uint64_t arSliceId(uint64_t idx, uint32_t slice_size) {
|
||||
return idx >> arSliceBits(slice_size);
|
||||
}
|
||||
|
||||
static inline uint32_t arSliceOff(uint64_t idx, uint32_t slice_size) {
|
||||
return (uint32_t)(idx & (slice_size - 1));
|
||||
}
|
||||
|
||||
static inline uint64_t arMakeIdx(uint64_t slice_id, uint32_t off, uint32_t slice_size) {
|
||||
return (slice_id << arSliceBits(slice_size)) | off;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Inline helpers: tagged value encoding
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
static inline int arIsEmpty(void *v) { return v == NULL; }
|
||||
|
||||
static inline int arIsPtr(void *v) {
|
||||
return v != NULL && ((uintptr_t)v & AR_TAG_MASK) == AR_TAG_PTR;
|
||||
}
|
||||
|
||||
static inline int arIsInt(void *v) {
|
||||
return ((uintptr_t)v & AR_TAG_MASK) == AR_TAG_INT;
|
||||
}
|
||||
|
||||
static inline int64_t arToInt(void *v) {
|
||||
return (int64_t)(intptr_t)v >> 2; /* Arithmetic shift preserves sign */
|
||||
}
|
||||
|
||||
static inline void *arFromInt(int64_t ival) {
|
||||
return (void *)(((uintptr_t)ival << 2) | AR_TAG_INT);
|
||||
}
|
||||
|
||||
static inline int arIntFits(int64_t ival) {
|
||||
#if UINTPTR_MAX == UINT64_MAX
|
||||
return ival >= -(1LL << 61) && ival <= (1LL << 61) - 1;
|
||||
#else
|
||||
return ival >= -(1LL << 29) && ival <= (1LL << 29) - 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline int arIsFloat(void *v) {
|
||||
return ((uintptr_t)v & AR_TAG_MASK) == AR_TAG_FLOAT;
|
||||
}
|
||||
|
||||
static inline double arToDouble(void *v) {
|
||||
#if UINTPTR_MAX == UINT64_MAX
|
||||
uint64_t bits = (uintptr_t)v & ~AR_TAG_MASK;
|
||||
double d;
|
||||
memcpy(&d, &bits, sizeof(d));
|
||||
return d;
|
||||
#else
|
||||
uint32_t bits = (uint32_t)((uintptr_t)v & ~(uintptr_t)AR_TAG_MASK);
|
||||
float f;
|
||||
memcpy(&f, &bits, sizeof(f));
|
||||
return (double)f;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void *arFromFloatBits(uint64_t bits_trunc) {
|
||||
#if UINTPTR_MAX == UINT64_MAX
|
||||
return (void *)((bits_trunc & ~AR_TAG_MASK) | AR_TAG_FLOAT);
|
||||
#else
|
||||
uint32_t bits32 = (uint32_t)bits_trunc;
|
||||
return (void *)(uintptr_t)((bits32 & ~(uint32_t)AR_TAG_MASK) | AR_TAG_FLOAT);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline int arIsSmallStr(void *v) {
|
||||
return ((uintptr_t)v & AR_TAG_MASK) == AR_TAG_STR;
|
||||
}
|
||||
|
||||
static inline int arSmallStrLen(void *v) {
|
||||
return (int)(((uintptr_t)v >> 2) & AR_SMALLSTR_LEN_MASK);
|
||||
}
|
||||
|
||||
static inline int arToSmallStr(void *v, char *buf) {
|
||||
int len = arSmallStrLen(v);
|
||||
uintptr_t val = (uintptr_t)v;
|
||||
for (int i = 0; i < len; i++) {
|
||||
buf[i] = (char)((val >> (8 * (i + 1))) & 0xFF);
|
||||
}
|
||||
buf[len] = '\0';
|
||||
return len;
|
||||
}
|
||||
|
||||
static inline void *arFromSmallStr(const char *s, int len) {
|
||||
uintptr_t v = AR_TAG_STR | ((uintptr_t)len << 2);
|
||||
for (int i = 0; i < len; i++) {
|
||||
v |= ((uintptr_t)(uint8_t)s[i]) << (8 * (i + 1));
|
||||
}
|
||||
return (void *)v;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Public API
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/* Lifecycle */
|
||||
redisArray *arNew(void);
|
||||
void arFree(redisArray *ar);
|
||||
redisArray *arDup(redisArray *ar);
|
||||
void arDismiss(redisArray *ar, size_t size_hint);
|
||||
|
||||
/* Element access */
|
||||
void *arGet(redisArray *ar, uint64_t idx);
|
||||
void arSet(redisArray *ar, uint64_t idx, void *v);
|
||||
int arDel(redisArray *ar, uint64_t idx);
|
||||
|
||||
/* Value encoding/decoding */
|
||||
void *arEncode(const char *s, size_t len);
|
||||
const char *arDecode(void *v, char *buf, size_t bufsize, size_t *outlen);
|
||||
int arFormatFloat(double d, char *buf, size_t bufsize);
|
||||
size_t arStringLen(const void *ptr);
|
||||
const char *arStringData(const void *ptr);
|
||||
void *arValueFromRdbInt(int64_t ival);
|
||||
void *arValueFromRdbFloat(double d);
|
||||
void *arValueFromRdbSmallStr(const char *s, size_t len);
|
||||
|
||||
/* Queries */
|
||||
uint64_t arCount(redisArray *ar);
|
||||
uint64_t arLen(redisArray *ar);
|
||||
|
||||
/* Bulk operations */
|
||||
uint64_t arDeleteRange(redisArray *ar, uint64_t lo, uint64_t hi);
|
||||
void arTruncate(redisArray *ar, uint64_t limit);
|
||||
void arMayPromoteToDenseForRangeSet(redisArray *ar, uint64_t lo, uint64_t hi);
|
||||
|
||||
/* Utilities */
|
||||
uint32_t arSparseFindPos(arSlice *s, uint16_t rel_idx, int *found);
|
||||
uint32_t arSuperDirFind(redisArray *ar, uint64_t block_id, int *found);
|
||||
redisArray *arDefrag(redisArray *ar, void *(*defragfn)(void *));
|
||||
unsigned long arDefragIncremental(redisArray **arref, unsigned long cursor,
|
||||
void *(*defragfn)(void *));
|
||||
|
||||
#endif /* __SPARSEARRAY_H */
|
||||
2021
src/t_array.c
Normal file
2021
src/t_array.c
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -91,6 +91,12 @@ static inline int log2ceil(size_t x) {
|
|||
#endif
|
||||
}
|
||||
|
||||
/* Return the smallest power of 2 >= count (e.g. 5 -> 8, 8 -> 8). */
|
||||
static inline int nearestNextPowerOf2(unsigned int count) {
|
||||
if (count <= 1) return 1;
|
||||
return 1 << (32 - __builtin_clz(count-1));
|
||||
}
|
||||
|
||||
/* Check for __builtin_add_overflow() */
|
||||
#ifndef __has_builtin
|
||||
#define __has_builtin(x) 0
|
||||
|
|
|
|||
|
|
@ -387,6 +387,10 @@ int zipmapValidateIntegrity(unsigned char *zm, size_t size, int deep) {
|
|||
|
||||
/* read the field name length */
|
||||
l = zipmapDecodeLength(p);
|
||||
/* Sanity check: length < 254 must be encoded in 1 byte, not 5 bytes */
|
||||
if (l < ZIPMAP_BIGLEN && s != 1)
|
||||
return 0;
|
||||
|
||||
p += s; /* skip the encoded field size */
|
||||
p += l; /* skip the field */
|
||||
|
||||
|
|
@ -402,6 +406,9 @@ int zipmapValidateIntegrity(unsigned char *zm, size_t size, int deep) {
|
|||
|
||||
/* read the value length */
|
||||
l = zipmapDecodeLength(p);
|
||||
/* Sanity check: length < 254 must be encoded in 1 byte, not 5 bytes */
|
||||
if (l < ZIPMAP_BIGLEN && s != 1)
|
||||
return 0;
|
||||
p += s; /* skip the encoded value size*/
|
||||
e = *p++; /* skip the encoded free space (always encoded in one byte) */
|
||||
p += l+e; /* skip the value and free space */
|
||||
|
|
|
|||
BIN
tests/assets/array-32bit.rdb
Normal file
BIN
tests/assets/array-32bit.rdb
Normal file
Binary file not shown.
|
|
@ -15,7 +15,7 @@ if { ! [ catch {
|
|||
|
||||
proc generate_collections {suffix elements} {
|
||||
set rd [redis_deferring_client]
|
||||
set numcmd 7
|
||||
set numcmd 8 ;# base commands including array
|
||||
set has_vsets [server_has_command vadd]
|
||||
if {$has_vsets} {incr numcmd}
|
||||
|
||||
|
|
@ -29,6 +29,15 @@ proc generate_collections {suffix elements} {
|
|||
$rd zadd zset$suffix $j $val
|
||||
$rd sadd set$suffix $val
|
||||
$rd xadd stream$suffix * item 1 value $val
|
||||
# Array with sparse indices and mixed value types (int, float, string)
|
||||
set idx [expr {$j * 100 + int(rand() * 50)}] ;# sparse indices
|
||||
if {$j % 3 == 0} {
|
||||
$rd arset array$suffix $idx $j ;# integer value
|
||||
} elseif {$j % 3 == 1} {
|
||||
$rd arset array$suffix $idx [format "%.5f" [expr {rand() * 1000}]] ;# float value
|
||||
} else {
|
||||
$rd arset array$suffix $idx "str_$val" ;# string value
|
||||
}
|
||||
if {$has_vsets} {
|
||||
$rd vadd vset$suffix VALUES 3 1 1 1 $j
|
||||
}
|
||||
|
|
@ -59,7 +68,9 @@ proc generate_types {} {
|
|||
# create other non-collection types
|
||||
r incr int
|
||||
r set string str
|
||||
if 0 {
|
||||
r gcra gcra 10 5 60000
|
||||
}
|
||||
|
||||
# create bigger objects with 10 items (more than a single ziplist / listpack)
|
||||
generate_collections big 10
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue