CEP-15 (C*): Messaging and storage engine integration

patch by Blake Eggleston; reviewed by Benedict Elliott Smith, David Capwell for CASSANDRA-17103
This commit is contained in:
Blake Eggleston 2022-10-21 13:10:08 -07:00 committed by David Capwell
parent e1823e0e22
commit 09c8fa1030
365 changed files with 29679 additions and 1043 deletions

37
.build/build-accord.xml Normal file
View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project basedir=".." name="apache-cassandra-accord-build"
xmlns:if="ant:if">
<target name="_build-accord" depends="init">
<exec executable="${basedir}/${accord.dir}/gradlew" dir="${basedir}/${accord.dir}" logError="true" failonerror="true" failifexecutionfails="true">
<!-- Need to call clean as a version change with a jar that didn't have code changes will not produce a new jar with the new version... so need clean to make sure build is stable -->
<arg value="clean" />
<arg value="build" />
<arg value="copyDependencies" /> <!-- For IDE support -->
<arg value="publishToMavenLocal" />
<arg value="-x" />
<arg value="test" />
<arg value="-x" />
<arg value="rat" />
<arg value="-Paccord_group=org.apache.cassandra" />
<arg value="-Paccord_artifactId=cassandra-accord" />
<arg value="-Paccord_version=${version}" />
</exec>
</target>
</project>

View File

@ -19,7 +19,7 @@
<project basedir="." name="apache-cassandra-checkstyle"
xmlns:rat="antlib:org.apache.rat.anttasks">
<target name="init-checkstyle" depends="resolver-retrieve-build,build-project" unless="no-checkstyle">
<target name="init-checkstyle" depends="resolver-retrieve-build,_build_subprojects,build-project" unless="no-checkstyle">
<path id="checkstyle.lib.path">
<fileset dir="${test.lib}/jars" includes="*.jar"/>
</path>
@ -45,7 +45,7 @@
</checkstyle>
</target>
<target name="checkstyle-test" depends="init-checkstyle,resolver-retrieve-build,build-project" description="Run custom checkstyle code analysis on tests" unless="no-checkstyle">
<target name="checkstyle-test" depends="init-checkstyle,resolver-retrieve-build,_build_subprojects,build-project" description="Run custom checkstyle code analysis on tests" unless="no-checkstyle">
<property name="checkstyle.log.dir" value="${build.dir}/checkstyle" />
<property name="checkstyle_test.report.file" value="${checkstyle.log.dir}/checkstyle_report_test.xml"/>
<mkdir dir="${checkstyle.log.dir}" />

View File

@ -78,6 +78,7 @@
<exclude NAME="CHANGES.txt"/>
<exclude NAME="CASSANDRA-14092.txt"/>
<exclude NAME="debian/TODO"/>
<exclude NAME="accord_demo.txt"/>
<!-- legal files -->
<exclude NAME="NOTICE.txt"/>
<exclude NAME="LICENSE.txt"/>

View File

@ -178,7 +178,7 @@
<property name="resolver-ant-tasks.initialized" value="true"/>
</target>
<target name="resolver-retrieve-build" depends="resolver-init,write-poms">
<target name="resolver-retrieve-build" depends="resolver-init,write-poms,_build_subprojects">
<resolvepom file="${build.dir}/${final.name}.pom" id="all-pom" />
<resolvepom file="${build.dir}/tmp-${final.name}-deps.pom" id="pom-deps" />
@ -206,7 +206,7 @@
</unzip>
</target>
<target name="resolver-dist-lib" depends="resolver-retrieve-build">
<target name="resolver-dist-lib" depends="resolver-retrieve-build,_build_subprojects">
<resolvepom file="${build.dir}/${final.name}.pom" id="all-pom" />
<retry retrycount="3" retrydelay="10" >

View File

@ -155,5 +155,10 @@
<groupId>org.bouncycastle</groupId>
<artifactId>bcutil-jdk18on</artifactId>
</dependency>
<dependency>
<groupId>org.apache.cassandra</groupId>
<artifactId>cassandra-accord</artifactId>
<classifier>tests</classifier>
</dependency>
</dependencies>
</project>

View File

@ -116,6 +116,10 @@
<groupId>org.mindrot</groupId>
<artifactId>jbcrypt</artifactId>
</dependency>
<dependency>
<groupId>org.apache.cassandra</groupId>
<artifactId>cassandra-accord</artifactId>
</dependency>
<dependency>
<groupId>io.airlift</groupId>
<artifactId>airline</artifactId>

View File

@ -0,0 +1,41 @@
#!/usr/bin/env bash
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Redirect output to stderr.
exec 1>&2
#set -o xtrace
set -o errexit
set -o pipefail
set -o nounset
bin="$(cd "$(dirname "$0")" > /dev/null; pwd)"
_main() {
# In case the usage happens at a different layer, make sure to cd to the toplevel
local root_dir
root_dir="$(git rev-parse --show-toplevel)"
cd "$root_dir"
if [[ ! -e .gitmodules ]]; then
# nothing to see here, look away!
return 0
fi
git submodule update --init --recursive
}
_main "$@"

View File

@ -0,0 +1 @@
post-checkout

View File

@ -0,0 +1,98 @@
#!/usr/bin/env bash
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##
## When working with submodules the top level project (Apache Cassandra) needs to commit all submodule
## changes so the top level knows what SHA to use. When working in a development environment it is
## common that multiple commits will exist in both projects, if the submodule has its history
## rewritten, then historic top level commits are no longer valid unless the SHAs are pushed to a
## remote repo; this is what the script attempts to do, make sure all SHAs added to the
## Apache Cassandra are backed up to a remote repo to make the Cassandra SHA buildable.
##
# Redirect output to stderr.
exec 1>&2
#set -o xtrace
set -o errexit
set -o pipefail
set -o nounset
bin="$(cd "$(dirname "$0")" > /dev/null; pwd)"
_log() {
echo -e "[pre-commit]\t$*"
}
error() {
_log "$@" 1>&2
exit 1
}
# Status Table
# A Added
# C Copied
# D Deleted
# M Modified
# R Renamed
# T Type Changed (i.e. regular file, symlink, submodule, …<200b>)
# U Unmerged
# X Unknown
# B Broken
_main() {
# In case the usage happens at a different layer, make sure to cd to the toplevel
local root_dir
root_dir="$(git rev-parse --show-toplevel)"
cd "$root_dir"
[[ ! -e .gitmodules ]] && return 0
local enabled=$(git config --bool cassandra.pre-commit.verify-submodules.enabled || echo true)
[ "$enabled" == "false" ] && return 0
local submodules=( $(git config --file .gitmodules --get-regexp path | awk '{ print $2 }') )
local is_submodule=false
local git_sub_dir
local git_sha
while read status file; do
is_submodule=false
for to_check in "${submodules[*]}"; do
if [[ "$to_check" == "$file" ]]; then
is_submodule=true
break
fi
done
if $is_submodule; then
local enabled=$(git config --bool cassandra.pre-commit.verify-submodule-${file}.enabled || echo true)
[ "$enabled" == "false" ] && continue
_log "Submodule detected: ${file} with status ${status}; attempting a push"
_log "\tTo disable pushes, run"
_log "\t\tgit config --local cassandra.pre-commit.verify-submodules.enabled false"
_log "\tOr"
_log "\t\tgit config --local cassandra.pre-commit.verify-submodule-${file}.enabled false"
set -x
git_sub_dir="${file}/.git"
branch="$(git config -f .gitmodules "submodule.${file}.branch")"
[[ -z "${branch:-}" ]] && error "Submodule ${file} does not define a branch"
git_sha="$(git --git-dir "${git_sub_dir}" rev-parse HEAD)"
git --git-dir "${git_sub_dir}" fetch origin
git --git-dir "${git_sub_dir}" branch "origin/${branch}" --contains "${git_sha}" || error "Git commit ${git_sha} not found in $(git remote get-url origin) on branch ${branch}"
fi
done < <(git diff --cached --name-status)
}
_main "$@"

View File

@ -0,0 +1,51 @@
#!/usr/bin/env bash
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Redirect output to stderr.
exec 1>&2
#set -o xtrace
set -o errexit
set -o pipefail
set -o nounset
bin="$(cd "$(dirname "$0")" > /dev/null; pwd)"
_main() {
# In case the usage happens at a different layer, make sure to cd to the toplevel
local root_dir
root_dir="$(git rev-parse --show-toplevel)"
cd "$root_dir"
if [[ ! -e .gitmodules ]]; then
# nothing to see here, look away!
return 0
fi
local -r cmd='
branch="$(git rev-parse --abbrev-ref HEAD)"
[[ "$branch" == "HEAD" ]] && exit 0
default_remote="$(git config --local --get branch."${branch}".remote || true)"
remote="${default_remote:-origin}"
git push --atomic "$remote" "$branch"
'
git submodule foreach --recursive "$cmd"
}
_main "$@"

View File

@ -0,0 +1,117 @@
#!/usr/bin/env bash
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#set -o xtrace
set -o errexit
set -o pipefail
set -o nounset
bin="$(cd "$(dirname "$0")" > /dev/null; pwd)"
install_template_script() {
local -r name="$1"
local -r d_dir="$2"
cat <<EOF > "$name"
#!/usr/bin/env bash
# This script is autogenerated by the Apache Cassandra build; DO NOT CHANGE!
# When this script is not found it will be installed automatically by the build
# If an existing script is found, that script will be reloated under ${d_dir} as 000-original.sh
# Redirect output to stderr.
exec 1>&2
# Find all scripts to run
for path in \$(find "$d_dir" -name '*.sh' | perl -e "print sort{(split '/', \\\$a)[-1] <=> (split '/', \\\$b)[-1]}<>"); do
"\$path" "\$@"
done
EOF
chmod a+x "$name"
}
install_hook() {
local -r git_dir="$1"
local -r hooks_dir="${git_dir}/hooks"
local -r name="$2"
local -r d_dir="${hooks_dir}/${name}.d"
local -r trigger_on_install=$3
mkdir "${d_dir}" &> /dev/null || true
local -r script_name="${hooks_dir}/${name}"
local installed=true
if [[ -e "$script_name" ]]; then
# was the script already installed?
if ! grep "This script is autogenerated by the Apache Cassandra build" "$script_name" &> /dev/null ; then
echo "$script_name found, but was not generated by the Apache Cassandra build; please remove or move to ${d_dir}/000-original.sh; creating and moving to ${d_dir} will cause it to run as expected, but won't conflict with hooks this build adds" 1>&2
exit 1
else
installed=false
fi
fi
# install all hooks
cp "$bin"/git-hooks/"${name}"/* "$d_dir"/
# install coordinator hook
install_template_script "$script_name" "$d_dir"
if $installed && $trigger_on_install ; then
echo "Running script $script_name"
"$script_name"
fi
}
_install_hooks() {
local git_dir
# make sure to use --git-common-dir and not --git-dir to support worktrees
git_dir="$(git rev-parse --git-common-dir 2> /dev/null || true)"
if [[ -z "${git_dir:-}" ]]; then
# not in a git repo, noop
return 0
fi
# make sure hooks directory exists; does not exist by default for worktrees
mkdir -p "${git_dir}/hooks" &> /dev/null || true
install_hook "$git_dir" "post-checkout" true
install_hook "$git_dir" "post-switch" false
install_hook "$git_dir" "pre-commit" false
install_hook "$git_dir" "pre-push" false
}
_git_config_set() {
local -r name="$1"
# only care about rc
git config --local --get "$name" &> /dev/null
}
_install_configs() {
# when doing pull, this makes sure submodules are updated
_git_config_set submodule.recurse || git config --local submodule.recurse true
}
_main() {
local git_dir
# make sure to use --git-common-dir and not --git-dir to support worktrees
git_dir="$(git rev-parse --git-common-dir 2> /dev/null || true)"
# not in a git repo, noop
[[ -z "${git_dir:-}" ]] && return 0
_install_configs
_install_hooks
}
_main "$@"

View File

@ -715,6 +715,42 @@
<artifactId>jbcrypt</artifactId>
<version>0.4</version>
</dependency>
<dependency>
<groupId>org.apache.cassandra</groupId>
<artifactId>cassandra-accord</artifactId>
<version>@version@</version>
<exclusions>
<exclusion>
<artifactId>org.apache.cassandra</artifactId>
<groupId>cassandra-all</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.cassandra</groupId>
<artifactId>cassandra-accord</artifactId>
<version>@version@</version>
<classifier>tests</classifier>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>org.junit.jupiter</artifactId>
<groupId>junit-jupiter-api</groupId>
</exclusion>
<exclusion>
<artifactId>org.junit.jupiter</artifactId>
<groupId>junit-jupiter-engine</groupId>
</exclusion>
<exclusion>
<artifactId>ch.qos.logback</artifactId>
<groupId>logback-classic</groupId>
</exclusion>
<exclusion>
<artifactId>org.apache.cassandra</artifactId>
<groupId>cassandra-all</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.airlift</groupId>
<artifactId>airline</artifactId>

38
.build/sh/bump-accord.sh Executable file
View File

@ -0,0 +1,38 @@
#!/usr/bin/env bash
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#set -o xtrace
set -o errexit
set -o pipefail
set -o nounset
_main() {
local home
home="$(git rev-parse --show-toplevel)"
cd "$home"
git submodule status modules/accord
echo "Is this the correct SHA? [y/n; default=y]"
read correct
if [[ "${correct:-y}" != "y" ]]; then
echo "Please update Accord's SHA and try again"
exit 1
fi
git commit -m "Change Accord to $(cd modules/accord; git log -1 --format='%h: %B')" modules/accord
}
_main "$@"

View File

@ -0,0 +1,25 @@
#!/usr/bin/env bash
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#set -o xtrace
set -o errexit
set -o pipefail
set -o nounset
bin="$(cd "$(dirname "$0")" > /dev/null; pwd)"
"$bin"/change-submodule.sh modules/accord 'https://github.com/apache/cassandra-accord.git' trunk

52
.build/sh/change-submodule.sh Executable file
View File

@ -0,0 +1,52 @@
#!/usr/bin/env bash
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#set -o xtrace
set -o errexit
set -o pipefail
set -o nounset
_usage() {
cat <<EOF
Usage: $(basename $0) [submodule path] [git repo URL] [git branch]
Examples:
Switch to branch "immutable-state"
.build/sh/change-submodule.sh modules/accord ../cassandra-accord.git immutable-state
EOF
exit 1
}
_main() {
if [[ $# -ne 3 ]]; then
_usage
fi
local -r path="$1"
local -r url="$2"
local -r branch="$3"
local home
home="$(git rev-parse --show-toplevel)"
cd "$home"
git submodule set-url "${path}" "${url}"
git submodule set-branch --branch "${branch}" "${path}"
git submodule update --remote
}
_main "$@"

117
.build/sh/development-switch.sh Executable file
View File

@ -0,0 +1,117 @@
#!/usr/bin/env bash
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#set -o xtrace
set -o errexit
set -o pipefail
set -o nounset
error() {
echo -e "$*" 1>&2
exit 1
}
_usage() {
cat <<EOF
Usage: $(basename $0) (options)* (submodule)*
Options:
--jira JIRA used for development, will checkout if not done yet
-h|--help This help page
EOF
exit 1
}
_is_main_branch() {
local -r name="$1"
[[ "$name" == cassandra-* ]] && return 0
[[ "$name" == "trunk" ]] && return 0
[[ "$name" == "cep-15-accord" ]] && return 0
return 1
}
_main() {
local home
home="$(git rev-parse --show-toplevel)"
cd "$home"
local branch
branch="$(git rev-parse --abbrev-ref HEAD)"
# loop over args, executing as in order of execution
while [ $# -gt 0 ]; do
case "$1" in
-h|--help)
_usage
;;
--jira)
if [[ "$2" != "$branch" ]]; then
git checkout -b "$2"
branch="$2"
fi
shift 2
;;
*)
break
;;
esac
done
while _is_main_branch "$branch" ; do
echo "Currently on $branch, which does not look like a development brarnch; what JIRA are you working on? "
read jira
if [[ ! -z "${jira:-}" ]]; then
git checkout -b "$jira"
branch="$jira"
break
fi
done
local submodules
submodules=( $(git config --file "$home"/.gitmodules --get-regexp path | awk '{ print $2 }') )
local to_change=()
if [[ $# -gt 0 ]]; then
local exists
for a in "$@"; do
exists=false
for sub in "${submodules[@]}"; do
if [[ "$sub" == "$a" ]]; then
exists=true
break
fi
done
[ "$exists" == false ] && error "git submodule $a does not exist"
to_change+=( "$a" )
done
else
for sub in "${submodules[@]}"; do
to_change+=( "$sub" )
done
fi
local submodule_branch
local name
for path in "${to_change[@]}"; do
name="$(basename "$path")"
git submodule set-url "${path}" "../cassandra-${name}.git"
git submodule set-branch --branch "${branch}" "${path}"
cd "$path"
submodule_branch="$(git rev-parse --abbrev-ref HEAD)"
if [[ "${submodule_branch}" != "${branch}" ]]; then
git checkout -b "$branch"
fi
cd -
done
}
_main "$@"

4
.gitmodules vendored Normal file
View File

@ -0,0 +1,4 @@
[submodule "modules/accord"]
path = modules/accord
url = https://github.com/apache/cassandra-accord.git
branch = trunk

View File

@ -1,4 +1,5 @@
5.1
* General Purpose Transactions (Accord) [CEP-15] (CASSANDRA-17092)
* Improve performance when getting writePlacementsAllSettled from ClusterMetadata (CASSANDRA-20526)
* Add nodetool command to dump the contents of the system_views.{cluster_metadata_log, cluster_metadata_directory} tables (CASSANDRA-20525)
* Fix TreeMap race in CollectionVirtualTableAdapter causing us to lose rows in the virtual table (CASSANDRA-20524)

View File

@ -27,6 +27,37 @@ In fact, this repository is a GitHub mirror of [the official repo](https://gitbo
Use [Cassandra JIRA](https://issues.apache.org/jira/browse/CASSANDRA/) to create an issue, then either attach a patch or post a link to a GitHub branch with your changes.
# Working with submodules
Apache Cassandra uses git submodules for a set of dependencies, this is to make cross cutting changes easier for developers. When working on such changes, there are a set of scripts to help with the process.
## Local Development
When starting a development branch, the following will change all submodules to a new branch based off the JIRA
```
$ .build/sh/development-switch.sh --jira CASSANDRA-<number>
```
When changes are made to a submodule (such as to accord), you need to commit and update the reference in Apache Cassandra
```
$ (cd modules/accord ; git commit -am 'Saving progress')
$ .build/sh/bump-accord.sh
```
## Commit and Merge Process
Due to the nature of submodules, the changes to the submodules must be committed and pushed before the changes to Apache Cassandra; these are different repositories so git's `--atomic` does not prevent conflicts from concurrent merges; the basic process is as follows:
* Follow the normal merge process for the submodule
* Update Apache Cassandra's submodule entry to point to the newly committed change; follow the Accord example below for an example
```
$ .build/sh/change-submodule-accord.sh
$ .build/sh/bump-accord.sh
```
# Useful Links
- How you can contribute to Apache Cassandra [presentation](http://www.slideshare.net/yukim/cassandrasummit2013) by Yuki Morishita

19
accord_demo.txt Normal file
View File

@ -0,0 +1,19 @@
ccm create accord-cql-poc -n 3
ccm start
bin/cqlsh -e "create keyspace ks with replication={'class':'SimpleStrategy', 'replication_factor':3};"
bin/cqlsh -e "create table ks.tbl1 (k int primary key, v int);"
bin/cqlsh -e "create table ks.tbl2 (k int primary key, v int);"
bin/nodetool -h 0000:0000:0000:0000:0000:ffff:7f00:0001 -p 7100 createepochunsafe
bin/nodetool -h 0000:0000:0000:0000:0000:ffff:7f00:0001 -p 7200 createepochunsafe
bin/nodetool -h 0000:0000:0000:0000:0000:ffff:7f00:0001 -p 7300 createepochunsafe
BEGIN TRANSACTION
LET row1 = (SELECT * FROM ks.tbl1 WHERE k = 1);
SELECT row1.v;
IF row1 IS NULL THEN
INSERT INTO ks.tbl1 (k, v) VALUES (1, 2);
END IF
COMMIT TRANSACTION;

View File

@ -100,6 +100,8 @@
the user specifies the tmp.dir property -->
<property name="tmp.dir" value="${build.dir}/tmp"/>
<property name="ant.build.src" value="${basedir}/.build" />
<property name="doc.dir" value="${basedir}/doc"/>
<condition property="version" value="${base.version}">
@ -109,8 +111,12 @@
<property name="version.properties.dir"
value="${build.src.resources}/org/apache/cassandra/config/" />
<property name="final.name" value="${ant.project.name}-${version}"/>
<property name="accord.dir" value="modules/accord" />
<!-- The reason not to use ant.project.name is we publish as "cassandra-accord" so the file names won't be named apache-cassandra-accord -->
<property name="accord.final.name" value="cassandra-accord-${version}"/>
<property name="local.repository" value="${user.home}/.m2/repository" />
<property name="accord.local.repository" value="${local.repository}/org/apache/cassandra/cassandra-accord/${version}" />
<!-- details of how and which Maven repository we publish to -->
<property name="maven.version" value="3.0.3" />
@ -396,6 +402,7 @@
<mkdir dir="${build.dir.lib}"/>
<mkdir dir="${jacoco.export.dir}"/>
<mkdir dir="${jacoco.partials.dir}"/>
<exec executable="${ant.build.src}/git/install-git-defaults.sh" osfamily="unix" dir="${basedir}" logError="true" failonerror="true" failifexecutionfails="true" />
<!-- Set up jdk specific properties -->
<javac includes="**/JdkProperties.java,**/SetSystemProperty.java" srcdir="test/anttasks" destdir="${test.classes}" includeantruntime="true" source="${java.default}" target="${java.default}">
@ -527,7 +534,8 @@
<!--
The build target builds all the .class files
-->
<target name="build" depends="resolver-retrieve-build,build-project" description="Compile Cassandra classes"/>
<target name="_build_subprojects" depends="_build-accord" description="Builds all subprojects needed for the build" />
<target name="build" depends="resolver-retrieve-build,_build_subprojects,build-project" description="Compile Cassandra classes"/>
<target name="codecoverage" depends="jacoco-run,jacoco-report" description="Create code coverage report"/>
<target name="_build_java">
@ -973,6 +981,9 @@
<exclude name=".externalToolBuilders/**" />
<!-- exclude NetBeans files -->
<exclude name="ide/nbproject/private/**" />
<!-- Accord build -->
<exclude name="${accord.dir}/*/build/**" />
<exclude name="${accord.dir}/**/.gradle/**" />
</tarfileset>
<!-- python driver -->
@ -992,6 +1003,7 @@
<include name="tools/bin/*"/>
<exclude name="tools/bin/*.in.sh" />
<include name=".build/**/*.sh"/>
<include name="${accord.dir}/gradlew" />
</tarfileset>
</tar>
@ -2061,6 +2073,7 @@
<target name="mvn-install"
depends="jar,sources-jar,javadoc-jar"
description="Installs the artifacts in the Maven Local Repository">
<!-- This logic does not install cassandra-accord as the accord submodule already did so -->
<!-- the parent -->
<install pomFile="${build.dir}/${final.name}-parent.pom"
@ -2082,6 +2095,18 @@
<target name="publish"
depends="mvn-install,artifacts"
description="Publishes the artifacts to the Maven repository">
<!-- Accord -->
<deploy pomFile="${accord.local.repository}/${accord.final.name}.pom"
file="${accord.local.repository}/${accord.final.name}.pom"
packaging="pom"/>
<deploy pomFile="${accord.local.repository}/${accord.final.name}.pom"
file="${accord.local.repository}/${accord.final.name}.jar" />
<deploy pomFile="${accord.local.repository}/${accord.final.name}.pom"
file="${accord.local.repository}/${accord.final.name}-sources.jar"
classifier="sources"/>
<deploy pomFile="${accord.local.repository}/${accord.final.name}.pom"
file="${accord.local.repository}/${accord.final.name}-javadoc.jar"
classifier="javadoc"/>
<!-- the parent -->
<deploy pomFile="${build.dir}/${final.name}-parent.pom"
@ -2112,4 +2137,5 @@
<import file="${build.helpers.dir}/build-cqlsh.xml"/>
<import file="${build.helpers.dir}/build-bench.xml"/>
<import file="${build.helpers.dir}/build-sonar.xml"/>
<import file="${build.helpers.dir}/build-accord.xml" />
</project>

View File

@ -2199,6 +2199,9 @@ drop_compact_storage_enabled: false
# Whether or not USE <keyspace> is allowed. This is enabled by default to avoid failure on upgrade.
#use_statements_enabled: true
# Enables the execution of Accord (multi-key) transactions on this node.
accord_transactions_enabled: false
# When the client triggers a protocol exception or unknown issue (Cassandra bug) we increment
# a client metric showing this; this logic will exclude specific subnets from updating these
# metrics

View File

@ -49,6 +49,16 @@
<excludeFolder url="file://$MODULE_DIR$/build" />
<excludeFolder url="file://$MODULE_DIR$/data"/>
<excludeFolder url="file://$MODULE_DIR$/logs"/>
<sourceFolder url="file://$MODULE_DIR$/modules/accord/accord-core/src/main/java" isTestSource="false"/>
<sourceFolder url="file://$MODULE_DIR$/modules/accord/accord-core/src/main/resources" ype="java-resource"/>
<sourceFolder url="file://$MODULE_DIR$/modules/accord/accord-core/src/test/java" isTestSource="true"/>
<sourceFolder url="file://$MODULE_DIR$/modules/accord/accord-core/src/test/resources" ype="java-test-resource"/>
<sourceFolder url="file://$MODULE_DIR$/modules/accord/accord-maelstrom/src/main/java" isTestSource="false"/>
<sourceFolder url="file://$MODULE_DIR$/modules/accord/accord-maelstrom/src/main/resources" ype="java-resource"/>
<sourceFolder url="file://$MODULE_DIR$/modules/accord/accord-maelstrom/src/test/java" isTestSource="true"/>
<sourceFolder url="file://$MODULE_DIR$/modules/accord/accord-maelstrom/src/test/resources" ype="java-test-resource"/>
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
@ -56,6 +66,8 @@
<library>
<CLASSES>
<root url="file://$MODULE_DIR$/build/lib/jars" />
<root url="file://$MODULE_DIR$/modules/accord/accord-core/build/dependencies/main/libs" />
<root url="file://$MODULE_DIR$/modules/accord/accord-maelstrom/build/dependencies/main/libs" />
</CLASSES>
<JAVADOC />
<SOURCES>
@ -63,12 +75,17 @@
</SOURCES>
<jarDirectory url="file://$MODULE_DIR$/build/lib/jars" recursive="false" />
<jarDirectory url="file://$MODULE_DIR$/build/lib/sources" recursive="false" type="SOURCES" />
<jarDirectory url="file://$MODULE_DIR$/modules/accord/accord-core/build/dependencies/main/libs" recursive="false" />
<jarDirectory url="file://$MODULE_DIR$/modules/accord/accord-maelstrom/build/dependencies/main/libs" recursive="false" />
</library>
</orderEntry>
<orderEntry type="module-library" scope="TEST">
<library>
<CLASSES>
<root url="file://$MODULE_DIR$/build/test/lib/jars" />
<root url="file://$MODULE_DIR$/modules/accord/accord-core/build/dependencies/test/libs" />
<root url="file://$MODULE_DIR$/modules/accord/accord-maelstrom/build/dependencies/test/libs" />
</CLASSES>
<JAVADOC />
<SOURCES>
@ -76,6 +93,9 @@
</SOURCES>
<jarDirectory url="file://$MODULE_DIR$/build/test/lib/jars" recursive="false" />
<jarDirectory url="file://$MODULE_DIR$/build/test/lib/sources" recursive="false" type="SOURCES" />
<jarDirectory url="file://$MODULE_DIR$/modules/accord/accord-core/build/dependencies/test/libs" recursive="false" />
<jarDirectory url="file://$MODULE_DIR$/modules/accord/accord-maelstrom/build/dependencies/test/libs" recursive="false" />
</library>
</orderEntry>
</component>

View File

@ -2,6 +2,7 @@
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/modules/accord" vcs="Git" />
</component>
<component name="IssueNavigationConfiguration">
<option name="links">

View File

@ -183,24 +183,38 @@
<option name="TEST_OBJECT" value="class" />
<option name="VM_PARAMETERS" value="
-Dcassandra.config=file://$PROJECT_DIR$/test/conf/cassandra.yaml
-Dcassandra.debugrefcount=false
-Dcassandra.logdir=$PROJECT_DIR$/build/test/logs
-Dcassandra.memtable_row_overhead_computation_step=100
-Dcassandra.reads.thresholds.coordinator.defensive_checks_enabled=true
-Dcassandra.ring_delay_ms=1000
-Dcassandra.ring_delay_ms=10000
-Dcassandra.skip_sync=true
-Dcassandra.strict.runtime.checks=true
-Dcassandra.test.flush_local_schema_changes=false
-Dcassandra.test.messagingService.nonGracefulShutdown=true
-Dcassandra.test.simulator.determinismcheck=strict
-Dcassandra.test.sstableformatdevelopment=true
-Dcassandra.tolerate_sstable_size=true
-Dcassandra.use_nix_recursive_delete=true
-Dinvalid-legacy-sstable-root=$PROJECT_DIR$/test/data/invalid-legacy-sstables
-Djava.library.path=$PROJECT_DIR$/lib/sigar-bin
-Djava.security.egd=file:/dev/urandom
-Dlegacy-sstable-root=$PROJECT_DIR$/test/data/legacy-sstables
-Dlogback.configurationFile=file://$PROJECT_DIR$/test/conf/logback-test.xml
-Dmigration-sstable-root=$PROJECT_DIR$/test/data/migration-sstables
-XX:ActiveProcessorCount=2
-javaagent:$PROJECT_DIR$/build/test/lib/jars/simulator-asm.jar
-Xbootclasspath/a:$PROJECT_DIR$/build/test/lib/jars/simulator-bootstrap.jar
-Xmx8G
-Xss384k
-XX:-BackgroundCompilation
-XX:-TieredCompilation
-XX:ActiveProcessorCount=4
-XX:CICompilerCount=1
-XX:HeapDumpPath=build/test
-XX:MaxMetaspaceSize=2G
-XX:SoftRefLRUPolicyMSPerMB=0
-Xmx4G
-XX:ReservedCodeCacheSize=256M
-XX:Tier4CompileThreshold=1000
-ea" />
<option name="PARAMETERS" value="" />
<fork_mode value="class" />

1
modules/accord Submodule

@ -0,0 +1 @@
Subproject commit b9025e59395f47535e4ed1fec20b1186cdb07db8

View File

@ -25,10 +25,10 @@ from cqlshlib import pylexotron, util
Hint = pylexotron.Hint
cql_keywords_reserved = {'add', 'allow', 'alter', 'and', 'apply', 'asc', 'authorize', 'batch', 'begin', 'by',
'columnfamily', 'create', 'delete', 'desc', 'describe', 'drop', 'entries', 'execute', 'from',
'full', 'grant', 'if', 'in', 'index', 'infinity', 'insert', 'into', 'is', 'keyspace', 'limit',
'columnfamily', 'create', 'commit', 'delete', 'desc', 'describe', 'drop', 'end', 'entries', 'execute', 'from',
'full', 'grant', 'if', 'in', 'index', 'infinity', 'insert', 'into', 'is', 'keyspace', 'let', 'limit',
'materialized', 'modify', 'nan', 'norecursive', 'not', 'null', 'of', 'on', 'or', 'order',
'primary', 'rename', 'revoke', 'schema', 'select', 'set', 'table', 'to', 'token', 'truncate',
'primary', 'rename', 'revoke', 'schema', 'select', 'set', 'table', 'then', 'to', 'token', 'transaction', 'truncate',
'unlogged', 'update', 'use', 'using', 'view', 'where', 'with'}
"""
Set of reserved keywords in CQL.
@ -147,7 +147,7 @@ class CqlParsingRuleSet(pylexotron.ParsingRuleSet):
else:
output.append(stmt)
if len(stmt) > 2:
if stmt[-3][1].upper() == 'APPLY':
if stmt[-3][1].upper() == 'APPLY' or stmt[0][1].upper() == 'COMMIT' or (stmt[0][1].upper() == 'END' and stmt[1][1].upper() == 'IF'):
in_batch = False
elif stmt[0][1].upper() == 'BEGIN':
in_batch = True

88
simulator.sh Executable file
View File

@ -0,0 +1,88 @@
#!/bin/bash
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#ant jar simulator-jars
DIR=`pwd`
JVM_OPTS="$JVM_OPTS -Dcassandra.config=file://$DIR/test/conf/cassandra.yaml"
JVM_OPTS="$JVM_OPTS -Dlogback.configurationFile=file://$DIR/test/conf/logback-simulator.xml"
JVM_OPTS="$JVM_OPTS -Dcassandra.logdir=$DIR/build/test/logs"
#JVM_OPTS="$JVM_OPTS -Djava.library.path=$DIR/lib/sigar-bin"
JVM_OPTS="$JVM_OPTS -Dlegacy-sstable-root=$DIR/test/data/legacy-sstables"
JVM_OPTS="$JVM_OPTS -Dinvalid-legacy-sstable-root=$DIR/test/data/invalid-legacy-sstables"
JVM_OPTS="$JVM_OPTS -Dcassandra.ring_delay_ms=1000"
JVM_OPTS="$JVM_OPTS -Dcassandra.skip_sync=true"
JVM_OPTS="$JVM_OPTS -ea"
JVM_OPTS="$JVM_OPTS -XX:MaxMetaspaceSize=1G"
JVM_OPTS="$JVM_OPTS -XX:SoftRefLRUPolicyMSPerMB=0"
JVM_OPTS="$JVM_OPTS -Dcassandra.strict.runtime.checks=true"
JVM_OPTS="$JVM_OPTS -javaagent:$DIR/build/test/lib/jars/simulator-asm.jar"
JVM_OPTS="$JVM_OPTS -Xbootclasspath/a:$DIR/build/test/lib/jars/simulator-bootstrap.jar"
JVM_OPTS="$JVM_OPTS -XX:ActiveProcessorCount=4"
JVM_OPTS="$JVM_OPTS -XX:-TieredCompilation"
JVM_OPTS="$JVM_OPTS -XX:Tier4CompileThreshold=1000"
JVM_OPTS="$JVM_OPTS -XX:ReservedCodeCacheSize=256M"
JVM_OPTS="$JVM_OPTS -Xmx8G"
JVM_OPTS="$JVM_OPTS -Dcassandra.test.simulator.determinismcheck=strict"
JVM_OPTS="$JVM_OPTS -Dcassandra.debugrefcount=false"
JVM_OPTS="$JVM_OPTS -Dcassandra.skip_sync=true"
JVM_OPTS="$JVM_OPTS -Dcassandra.tolerate_sstable_size=true"
JVM_OPTS="$JVM_OPTS -Dcassandra.test.simulator.debug=true"
JVM_OPTS="$JVM_OPTS -Dcassandra.test.simulator.determinismcheck=strict"
echo $JVM_OPTS
CLASSPATH="$DIR"/build/test/classes
for dir in "$DIR"/build/classes/*; do
CLASSPATH="$CLASSPATH:$dir"
done
for jar in "$DIR"/lib/*.jar; do
CLASSPATH="$CLASSPATH:$jar"
done
for jar in "$DIR"/build/*.jar; do
if [[ $jar != *"logback-classic"* ]]; then
CLASSPATH="$CLASSPATH:$jar"
fi
done
for jar in "$DIR"/build/lib/jars/*.jar; do
if [[ $jar != *"logback-classic"* ]]; then
CLASSPATH="$CLASSPATH:$jar"
fi
done
for jar in "$DIR"/build/test/lib/jars/*.jar; do
if [[ $jar != *"logback-classic"* ]]; then
CLASSPATH="$CLASSPATH:$jar"
fi
done
CLASS="org.apache.cassandra.simulator.paxos.AccordSimulationRunner"
OPTS="run -n 3..6 -t 1000 --cluster-action-limit -1 -c 2 -s 30"
echo "java -cp <...> $CLASS $OPTS $@"
while true
do
echo ""
java -cp $CLASSPATH $JVM_OPTS $CLASS $OPTS $@
status=$?
if [ $status -ne 0 ] ; then
exit $status
fi
done

View File

@ -46,6 +46,7 @@ import Parser,Lexer;
import org.apache.cassandra.cql3.statements.*;
import org.apache.cassandra.cql3.statements.schema.*;
import org.apache.cassandra.cql3.terms.*;
import org.apache.cassandra.cql3.transactions.*;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.exceptions.SyntaxException;

View File

@ -60,6 +60,7 @@ lexer grammar Lexer;
// pylib/cqlshlib/cqlhandling.py::cql_keywords_reserved.
// When adding a new unreserved keyword, add entry to unreserved keywords in Parser.g.
K_SELECT: S E L E C T;
K_LET: L E T;
K_FROM: F R O M;
K_AS: A S;
K_WHERE: W H E R E;
@ -83,8 +84,10 @@ K_BEGIN: B E G I N;
K_UNLOGGED: U N L O G G E D;
K_BATCH: B A T C H;
K_APPLY: A P P L Y;
K_COMMIT: C O M M I T;
K_TRUNCATE: T R U N C A T E;
K_DELETE: D E L E T E;
K_TRANSACTION: T R A N S A C T I O N;
K_IN: I N;
K_CREATE: C R E A T E;
K_SCHEMA: S C H E M A;
@ -123,6 +126,8 @@ K_DESC: D E S C;
K_ALLOW: A L L O W;
K_FILTERING: F I L T E R I N G;
K_IF: I F;
K_THEN: T H E N;
K_END: E N D;
K_IS: I S;
K_CONTAINS: C O N T A I N S;
K_BETWEEN: B E T W E E N;

View File

@ -27,6 +27,13 @@ options {
private final List<ErrorListener> listeners = new ArrayList<ErrorListener>();
protected final List<ColumnIdentifier> bindVariables = new ArrayList<ColumnIdentifier>();
// enables parsing txn specific syntax when true
protected boolean isParsingTxn = false;
// tracks whether a txn has conditional updates
protected boolean isTxnConditional = false;
protected List<RowDataReference.Raw> references;
public static final Set<String> reservedTypeNames = new HashSet<String>()
{{
add("byte");
@ -59,6 +66,19 @@ options {
return marker;
}
public RowDataReference.Raw newRowDataReference(Selectable.RawIdentifier tuple, Selectable.Raw selectable)
{
if (!isParsingTxn)
throw new SyntaxException("Cannot create a row data reference unless parsing a transaction");
if (references == null)
references = new ArrayList<>();
RowDataReference.Raw reference = RowDataReference.Raw.fromSelectable(tuple, selectable);
references.add(reference);
return reference;
}
public void addErrorListener(ErrorListener listener)
{
this.listeners.add(listener);
@ -122,14 +142,24 @@ options {
return res;
}
public void addRawUpdate(List<Pair<ColumnIdentifier, Operation.RawUpdate>> operations, ColumnIdentifier key, Operation.RawUpdate update)
public void addRawUpdate(UpdateStatement.OperationCollector collector, ColumnIdentifier key, Operation.RawUpdate update)
{
for (Pair<ColumnIdentifier, Operation.RawUpdate> p : operations)
{
if (p.left.equals(key) && !p.right.isCompatibleWith(update))
addRecognitionError("Multiple incompatible setting of column " + key);
}
operations.add(Pair.create(key, update));
if (collector.conflictsWithExistingUpdate(key, update))
addRecognitionError("Multiple incompatible setting of column " + key);
if (collector.conflictsWithExistingSubstitution(key))
addRecognitionError("Normal and reference operations for " + key);
collector.addRawUpdate(key, update);
}
public void addRawReferenceOperation(UpdateStatement.OperationCollector collector, ColumnIdentifier key, ReferenceOperation.Raw update)
{
if (collector.conflictsWithExistingUpdate(key))
addRecognitionError("Multiple incompatible setting of column " + key);
if (collector.conflictsWithExistingSubstitution(key))
addRecognitionError("Normal and reference operations for " + key);
collector.addRawReferenceOperation(key, update);
}
public Set<Permission> filterPermissions(Set<Permission> permissions, IResource resource)
@ -237,6 +267,8 @@ cqlStatement returns [CQLStatement.Raw stmt]
| st43=dropIdentityStatement { $stmt = st43; }
| st44=listSuperUsersStatement { $stmt = st44; }
| st45=copyTableStatement { $stmt = st45; }
| st46=batchTxnStatement { $stmt = st46; }
| st47=letStatement { $stmt = st47; }
;
/*
@ -276,12 +308,40 @@ selectStatement returns [SelectStatement.RawStatement expr]
groups,
$sclause.isDistinct,
allowFiltering,
isJson);
isJson,
null);
WhereClause where = wclause == null ? WhereClause.empty() : wclause.build();
$expr = new SelectStatement.RawStatement(cf, params, $sclause.selectors, where, limit, perPartitionLimit);
}
;
/**
* ex. LET x = (SELECT * FROM <table> WHERE k=1 AND c=2)
* ex. LET y = (SELECT * FROM <table> WHERE k=1 LIMIT 1)
*/
letStatement returns [SelectStatement.RawStatement expr]
@init {
Term.Raw limit = null;
}
: K_LET txnVar=IDENT '='
'(' K_SELECT assignments=letSelectors K_FROM cf=columnFamilyName K_WHERE wclause=whereClause ( K_LIMIT rows=intValue { limit = rows; } )? ')'
{
SelectStatement.Parameters params = new SelectStatement.Parameters(Collections.emptyList(), Collections.emptyList(), false, false, false, $txnVar.text);
WhereClause where = wclause == null ? WhereClause.empty() : wclause.build();
$expr = new SelectStatement.RawStatement(cf, params, assignments, where, limit, null);
}
;
letSelectors returns [List<RawSelector> expr]
: t1=letSelector { $expr = new ArrayList<RawSelector>(); $expr.add(t1); } (',' tN=letSelector { $expr.add(tN); })*
| '\*' { $expr = Collections.<RawSelector>emptyList();}
;
letSelector returns [RawSelector s]
@init{ ColumnIdentifier alias = null; }
: us=unaliasedSelector { $s = new RawSelector(us, alias); }
;
selectClause returns [boolean isDistinct, List<RawSelector> selectors]
@init{ $isDistinct = false; }
// distinct is a valid column name. By consequence, we need to resolve the ambiguity for "distinct - distinct"
@ -489,7 +549,7 @@ normalInsertStatement [QualifiedName qn] returns [UpdateStatement.ParsedInsert e
}
: '(' c1=cident { columnNames.add(c1); } ( ',' cn=cident { columnNames.add(cn); } )* ')'
K_VALUES
'(' v1=term { values.add(v1); } ( ',' vn=term { values.add(vn); } )* ')'
'(' insertValue[values] ( ',' insertValue[values] )* ')'
( K_IF K_NOT K_EXISTS { ifNotExists = true; } )?
( usingClause[attrs] )?
{
@ -497,6 +557,11 @@ normalInsertStatement [QualifiedName qn] returns [UpdateStatement.ParsedInsert e
}
;
insertValue[List<Term.Raw> values]
: t=term { values.add(t); }
| {isParsingTxn}? dr=rowDataReference { values.add(new ReferenceValue.Substitution.Raw(dr)); }
;
jsonInsertStatement [QualifiedName qn] returns [UpdateStatement.ParsedInsertJson expr]
@init {
Attributes.Raw attrs = new Attributes.Raw();
@ -537,7 +602,7 @@ usingClauseObjective[Attributes.Raw attrs]
updateStatement returns [UpdateStatement.ParsedUpdate expr]
@init {
Attributes.Raw attrs = new Attributes.Raw();
List<Pair<ColumnIdentifier, Operation.RawUpdate>> operations = new ArrayList<>();
UpdateStatement.OperationCollector operations = new UpdateStatement.OperationCollector();
boolean ifExists = false;
}
: K_UPDATE cf=columnFamilyName
@ -551,7 +616,8 @@ updateStatement returns [UpdateStatement.ParsedUpdate expr]
operations,
wclause.build(),
conditions == null ? Collections.<ColumnCondition.Raw>emptyList() : conditions,
ifExists);
ifExists,
isParsingTxn);
}
;
@ -650,6 +716,102 @@ batchStatementObjective returns [ModificationStatement.Parsed statement]
| d=deleteStatement { $statement = d; }
;
/**
* ex. conditional update returning pre-update values
*
* BEGIN TRANSACTION
* LET row1 = (SELECT * FROM <table> WHERE k=1 AND c=2);
* LET row2 = (SELECT * FROM <table> WHERE k=2 AND c=2);
* SELECT row1.v, row2.v;
* IF row1.v = 3 AND row2.v = 4 THEN
* UPDATE <table> SET v = row1.v + 1 WHERE k = 1 AND c = 2;
* END IF
* COMMIT TRANSACTION
*
* ex. read-only transaction
*
* BEGIN TRANSACTION
* SELECT * FROM <table> WHERE k=1 AND c=2;
* COMMIT TRANSACTION
*
* ex. write-only transaction
*
* BEGIN TRANSACTION
* INSERT INTO <table> (k, c, v) VALUES (0, 0, 1);
* COMMIT TRANSACTION
*/
batchTxnStatement returns [TransactionStatement.Parsed expr]
@init {
isParsingTxn = true;
List<SelectStatement.RawStatement> assignments = new ArrayList<>();
SelectStatement.RawStatement select = null;
List<RowDataReference.Raw> returning = null;
List<ModificationStatement.Parsed> updates = new ArrayList<>();
}
: K_BEGIN K_TRANSACTION
( let=letStatement ';' { assignments.add(let); })*
( ( (selectStatement) => s=selectStatement ';' { select = s; }) | ( K_SELECT drs=rowDataReferences ';' { returning = drs; }) )?
( K_IF conditions=txnConditions K_THEN { isTxnConditional = true; } )?
( upd=batchStatementObjective ';' { updates.add(upd); } )*
( {!isTxnConditional}? (K_COMMIT K_TRANSACTION) | {isTxnConditional}? (K_END K_IF K_COMMIT K_TRANSACTION))
{
$expr = new TransactionStatement.Parsed(assignments, select, returning, updates, conditions, references);
}
;
finally { isParsingTxn = false; }
rowDataReferences returns [List<RowDataReference.Raw> refs]
: r1=rowDataReference { refs = new ArrayList<RowDataReference.Raw>(); refs.add(r1); } (',' rN=rowDataReference { refs.add(rN); })*
;
rowDataReference returns [RowDataReference.Raw rawRef]
@init { Selectable.RawIdentifier tuple = null; Selectable.Raw selectable = null; }
@after { $rawRef = newRowDataReference(tuple, selectable); }
: t=sident ('.' s=referenceSelection)? { tuple = t; selectable = s; }
;
referenceSelection returns [Selectable.Raw s]
: g=referenceSelectionWithoutField m=selectorModifier[g] {$s = m;}
;
referenceSelectionWithoutField returns [Selectable.Raw s]
@init { Selectable.Raw tmp = null; }
@after { $s = tmp; }
: sn=sident { tmp=sn; }
| (selectionTypeHint)=> h=selectionTypeHint { tmp=h; }
| t=selectionTupleOrNestedSelector { tmp=t; }
| l=selectionList { tmp=l; }
| m=selectionMapOrSet { tmp=m; }
// UDTs are equivalent to maps from the syntax point of view, so the final decision will be done in Selectable.WithMapOrUdt
;
txnConditions returns [List<ConditionStatement.Raw> conditions]
@init { conditions = new ArrayList<ConditionStatement.Raw>(); }
: txnColumnCondition[conditions] ( K_AND txnColumnCondition[conditions] )*
;
txnConditionKind returns [ConditionStatement.Kind op]
: '=' { $op = ConditionStatement.Kind.EQ; }
| '<' { $op = ConditionStatement.Kind.LT; }
| '<=' { $op = ConditionStatement.Kind.LTE; }
| '>' { $op = ConditionStatement.Kind.GT; }
| '>=' { $op = ConditionStatement.Kind.GTE; }
| '!=' { $op = ConditionStatement.Kind.NEQ; }
;
txnColumnCondition[List<ConditionStatement.Raw> conditions]
: lhs=rowDataReference
(
K_IS
(
K_NOT K_NULL { conditions.add(new ConditionStatement.Raw(lhs, ConditionStatement.Kind.IS_NOT_NULL, null)); }
| K_NULL { conditions.add(new ConditionStatement.Raw(lhs, ConditionStatement.Kind.IS_NULL, null)); }
)
| (txnConditionKind term)=> op=txnConditionKind t=term { conditions.add(new ConditionStatement.Raw(lhs, op, t)); }
)
| lhs=term op=txnConditionKind rhs=rowDataReference { conditions.add(new ConditionStatement.Raw(lhs, op, rhs)); }
;
createAggregateStatement returns [CreateAggregateStatement.Raw stmt]
@init {
boolean orReplace = false;
@ -1727,18 +1889,18 @@ simpleTerm returns [Term.Raw term]
| K_CAST '(' t=simpleTerm K_AS n=native_type ')' { $term = FunctionCall.Raw.newCast(t, n); }
;
columnOperation[List<Pair<ColumnIdentifier, Operation.RawUpdate>> operations]
columnOperation[UpdateStatement.OperationCollector operations]
: key=cident columnOperationDifferentiator[operations, key]
;
columnOperationDifferentiator[List<Pair<ColumnIdentifier, Operation.RawUpdate>> operations, ColumnIdentifier key]
columnOperationDifferentiator[UpdateStatement.OperationCollector operations, ColumnIdentifier key]
: '=' normalColumnOperation[operations, key]
| shorthandColumnOperation[operations, key]
| '[' k=term ']' collectionColumnOperation[operations, key, k]
| '.' field=fident udtColumnOperation[operations, key, field]
;
normalColumnOperation[List<Pair<ColumnIdentifier, Operation.RawUpdate>> operations, ColumnIdentifier key]
normalColumnOperation[UpdateStatement.OperationCollector operations, ColumnIdentifier key]
: t=term ('+' c=cident )?
{
if (c == null)
@ -1766,27 +1928,56 @@ normalColumnOperation[List<Pair<ColumnIdentifier, Operation.RawUpdate>> operatio
addRecognitionError("Only expressions of the form X = X " + ($i.text.charAt(0) == '-' ? '-' : '+') + " <value> are supported.");
addRawUpdate(operations, key, new Operation.Addition(Constants.Literal.integer($i.text)));
}
| {isParsingTxn}? r=rowDataReference
{
addRawReferenceOperation(operations, key, new ReferenceOperation.Raw(new Operation.SetValue(r), key, new ReferenceValue.Substitution.Raw(r)));
}
;
shorthandColumnOperation[List<Pair<ColumnIdentifier, Operation.RawUpdate>> operations, ColumnIdentifier key]
: sig=('+=' | '-=') t=term
{
addRawUpdate(operations, key, $sig.text.equals("+=") ? new Operation.Addition(t) : new Operation.Substraction(t));
}
shorthandColumnOperation[UpdateStatement.OperationCollector operations, ColumnIdentifier key]
: sig=('+=' | '-=')
(
t=term
{
addRawUpdate(operations, key, $sig.text.equals("+=") ? new Operation.Addition(t) : new Operation.Substraction(t));
}
| {isParsingTxn}? dr=rowDataReference
{
ReferenceValue.Raw right = new ReferenceValue.Substitution.Raw(dr);
Operation.RawUpdate operation = $sig.text.equals("+=") ? new Operation.Addition(dr) : new Operation.Substraction(dr);
addRawReferenceOperation(operations, key, new ReferenceOperation.Raw(operation, key, right));
}
)
;
collectionColumnOperation[List<Pair<ColumnIdentifier, Operation.RawUpdate>> operations, ColumnIdentifier key, Term.Raw k]
: '=' t=term
{
addRawUpdate(operations, key, new Operation.SetElement(k, t));
}
collectionColumnOperation[UpdateStatement.OperationCollector operations, ColumnIdentifier key, Term.Raw k]
: '='
(
t=term
{
addRawUpdate(operations, key, new Operation.SetElement(k, t));
}
| {isParsingTxn}? dr=rowDataReference
{
ReferenceValue.Raw right = new ReferenceValue.Substitution.Raw(dr);
addRawReferenceOperation(operations, key, new ReferenceOperation.Raw(new Operation.SetElement(k, dr), key, right));
}
)
;
udtColumnOperation[List<Pair<ColumnIdentifier, Operation.RawUpdate>> operations, ColumnIdentifier key, FieldIdentifier field]
: '=' t=term
{
addRawUpdate(operations, key, new Operation.SetField(field, t));
}
udtColumnOperation[UpdateStatement.OperationCollector operations, ColumnIdentifier key, FieldIdentifier field]
: '='
(
t=term
{
addRawUpdate(operations, key, new Operation.SetField(field, t));
}
| {isParsingTxn}? dr=rowDataReference
{
ReferenceValue.Raw right = new ReferenceValue.Substitution.Raw(dr);
addRawReferenceOperation(operations, key, new ReferenceOperation.Raw(new Operation.SetField(field, dr), key, right));
}
)
;
columnCondition returns [ColumnCondition.Raw condition]

View File

@ -23,5 +23,5 @@ package org.apache.cassandra.audit;
*/
public enum AuditLogEntryCategory
{
QUERY, DML, DDL, DCL, OTHER, AUTH, ERROR, PREPARE, JMX
QUERY, DML, DDL, DCL, OTHER, AUTH, ERROR, PREPARE, JMX, TRANSACTION
}

View File

@ -63,6 +63,7 @@ public enum AuditLogEntryType
DROP_IDENTITY(AuditLogEntryCategory.DCL),
USE_KEYSPACE(AuditLogEntryCategory.OTHER),
DESCRIBE(AuditLogEntryCategory.OTHER),
TRANSACTION(AuditLogEntryCategory.TRANSACTION),
/*
* Common Audit Log Entry Types

View File

@ -28,7 +28,7 @@ final class AuditLogFilter
{
private static final Logger logger = LoggerFactory.getLogger(AuditLogFilter.class);
private static ImmutableSet<String> EMPTY_FILTERS = ImmutableSet.of();
private static final ImmutableSet<String> EMPTY_FILTERS = ImmutableSet.of();
final ImmutableSet<String> excludedKeyspaces;
final ImmutableSet<String> includedKeyspaces;

View File

@ -25,6 +25,7 @@ public class SingleThreadExecutorPlus extends ThreadPoolExecutorPlus implements
{
public static class AtLeastOnce extends AtomicBoolean implements AtLeastOnceTrigger, Runnable
{
private static final long serialVersionUID = 0; // for simulator support
protected final SequentialExecutorPlus executor;
protected final Runnable run;

View File

@ -47,6 +47,7 @@ public enum Stage
MUTATION (true, "MutationStage", "request", DatabaseDescriptor::getConcurrentWriters, DatabaseDescriptor::setConcurrentWriters, Stage::multiThreadedLowSignalStage),
COUNTER_MUTATION (true, "CounterMutationStage", "request", DatabaseDescriptor::getConcurrentCounterWriters, DatabaseDescriptor::setConcurrentCounterWriters, Stage::multiThreadedLowSignalStage),
VIEW_MUTATION (true, "ViewMutationStage", "request", DatabaseDescriptor::getConcurrentViewWriters, DatabaseDescriptor::setConcurrentViewWriters, Stage::multiThreadedLowSignalStage),
ACCORD (true, "AccordStage", "request", DatabaseDescriptor::getConcurrentAccordOps, DatabaseDescriptor::setConcurrentAccordOps, Stage::multiThreadedLowSignalStage),
GOSSIP (true, "GossipStage", "internal", () -> 1, null, Stage::singleThreadedStage),
REQUEST_RESPONSE (false, "RequestResponseStage", "request", FBUtilities::getAvailableProcessors, null, Stage::multiThreadedLowSignalStage),
ANTI_ENTROPY (false, "AntiEntropyStage", "internal", () -> 1, null, Stage::singleThreadedStage),
@ -59,7 +60,6 @@ public enum Stage
INTERNAL_METADATA (false, "InternalMetadataStage", "internal", FBUtilities::getAvailableProcessors, null, Stage::multiThreadedStage),
FETCH_LOG (false, "MetadataFetchLogStage", "internal", () -> 1, null, Stage::singleThreadedStage)
;
public final String jmxName;
private final Supplier<ExecutorPlus> executorSupplier;
private volatile ExecutorPlus executor;

View File

@ -491,6 +491,8 @@ public enum CassandraRelevantProperties
SERIALIZATION_EMPTY_TYPE_NONEMPTY_BEHAVIOR("cassandra.serialization.emptytype.nonempty_behavior"),
SET_SEP_THREAD_NAME("cassandra.set_sep_thread_name", "true"),
SHUTDOWN_ANNOUNCE_DELAY_IN_MS("cassandra.shutdown_announce_in_ms", "2000"),
SIMULATOR_SEED("cassandra.simulator.seed"),
SIMULATOR_STARTED("cassandra.simulator.started"),
SIZE_RECORDER_INTERVAL("cassandra.size_recorder_interval", "300"),
SKIP_AUTH_SETUP("cassandra.skip_auth_setup", "false"),
SKIP_GC_INSPECTOR("cassandra.skip_gc_inspector", "false"),
@ -595,6 +597,7 @@ public enum CassandraRelevantProperties
TEST_READ_ITERATION_DELAY_MS("cassandra.test.read_iteration_delay_ms", "0"),
TEST_REUSE_PREPARED("cassandra.test.reuse_prepared", "true"),
TEST_ROW_CACHE_SIZE("cassandra.test.row_cache_size"),
TEST_SEED("cassandra.test.seed"),
TEST_SERIALIZATION_WRITES("cassandra.test-serialization-writes"),
TEST_SIMULATOR_DEBUG("cassandra.test.simulator.debug"),
TEST_SIMULATOR_DETERMINISM_CHECK("cassandra.test.simulator.determinismcheck", "none"),

View File

@ -174,6 +174,8 @@ public class Config
public volatile DurationSpec.LongMillisecondsBound stream_transfer_task_timeout = new DurationSpec.LongMillisecondsBound("12h");
public volatile DurationSpec.LongMillisecondsBound transaction_timeout = new DurationSpec.LongMillisecondsBound("30s");
public volatile DurationSpec.LongMillisecondsBound cms_await_timeout = new DurationSpec.LongMillisecondsBound("120000ms");
public volatile int cms_default_max_retries = 10;
public volatile DurationSpec.IntMillisecondsBound cms_default_retry_backoff = new DurationSpec.IntMillisecondsBound("50ms");
@ -188,6 +190,7 @@ public class Config
public int concurrent_reads = 32;
public int concurrent_writes = 32;
public int concurrent_accord_operations = 32;
public int concurrent_counter_writes = 32;
public int concurrent_materialized_view_writes = 32;
public int available_processors = -1;
@ -620,6 +623,8 @@ public class Config
public volatile boolean use_statements_enabled = true;
public boolean accord_transactions_enabled = false;
/**
* Optionally disable asynchronous UDF execution.
* Disabling asynchronous UDF execution also implicitly disables the security-manager!
@ -1159,6 +1164,8 @@ public class Config
public volatile boolean client_request_size_metrics_enabled = true;
public LegacyPaxosStrategy legacy_paxos_strategy = LegacyPaxosStrategy.migration;
public volatile int max_top_size_partition_count = 10;
public volatile int max_top_tombstone_partition_count = 10;
public volatile DataStorageSpec.LongBytesBound min_tracked_partition_size = new DataStorageSpec.LongBytesBound("1MiB");
@ -1374,6 +1381,29 @@ public class Config
cell
}
/**
* How to pick a consensus protocol for CAS
* and serial read operations. Transaction statements
* will always run on Accord. Legacy in this context includes PaxosV2.
*/
public enum LegacyPaxosStrategy
{
/**
* Allow both Accord and PaxosV1/V2 to run on the same cluster
* Some keys and ranges might be running on Accord if they
* have been migrated and the rest will run on Paxos until
* they are migrated.
*/
migration,
/**
* Everything will be run on Accord. Useful for new deployments
* that don't want to accidentally start using legacy Paxos
* requiring migration to Accord.
*/
accord
}
private static final Set<String> SENSITIVE_KEYS = new HashSet<String>() {{
add("client_encryption_options");
add("server_encryption_options");
@ -1400,10 +1430,10 @@ public class Config
String value;
try
{
// Field.get() can throw NPE if the value of the field is null
value = field.get(config).toString();
Object obj = field.get(config);
value = obj != null ? obj.toString() : "null";
}
catch (NullPointerException | IllegalAccessException npe)
catch (IllegalAccessException npe)
{
value = "null";
}

View File

@ -166,6 +166,9 @@ import static org.apache.cassandra.utils.Clock.Global.logInitializationOutcome;
public class DatabaseDescriptor
{
public static final String NO_ACCORD_PAXOS_STRATEGY_WITH_ACCORD_DISABLED_MESSAGE =
"Cannot use legacy_paxos_strategy \"accord\" while Accord transactions are disabled.";
static
{
CHRONICLE_ANALYTICS_DISABLE.setBoolean(true);
@ -640,6 +643,9 @@ public class DatabaseDescriptor
if (conf.concurrent_counter_writes < 2)
throw new ConfigurationException("concurrent_counter_writes must be at least 2, but was " + conf.concurrent_counter_writes, false);
if (conf.concurrent_accord_operations < 1)
throw new ConfigurationException("concurrent_accord_operations must be at least 1, but was " + conf.concurrent_accord_operations, false);
if (conf.networking_cache_size == null)
conf.networking_cache_size = new DataStorageSpec.IntMebibytesBound(Math.min(128, (int) (Runtime.getRuntime().maxMemory() / (16 * 1048576))));
@ -1116,6 +1122,9 @@ public class DatabaseDescriptor
// run audit logging options through sanitation and validation
if (conf.audit_logging_options != null)
setAuditLoggingOptions(conf.audit_logging_options);
if (conf.legacy_paxos_strategy == Config.LegacyPaxosStrategy.accord && !conf.accord_transactions_enabled)
throw new ConfigurationException(NO_ACCORD_PAXOS_STRATEGY_WITH_ACCORD_DISABLED_MESSAGE);
}
@VisibleForTesting
@ -1473,6 +1482,12 @@ public class DatabaseDescriptor
logInfo("truncate_request_timeout", conf.truncate_request_timeout, LOWEST_ACCEPTED_TIMEOUT);
conf.truncate_request_timeout = LOWEST_ACCEPTED_TIMEOUT;
}
if (conf.transaction_timeout.toMilliseconds() < LOWEST_ACCEPTED_TIMEOUT.toMilliseconds())
{
logInfo("transaction_timeout", conf.transaction_timeout, LOWEST_ACCEPTED_TIMEOUT);
conf.transaction_timeout = LOWEST_ACCEPTED_TIMEOUT;
}
}
private static void logInfo(String property, DurationSpec.LongMillisecondsBound actualValue, DurationSpec.LongMillisecondsBound lowestAcceptedValue)
@ -2427,6 +2442,16 @@ public class DatabaseDescriptor
return conf.cas_contention_timeout.to(unit);
}
public static long getTransactionTimeout(TimeUnit unit)
{
return conf.transaction_timeout.to(unit);
}
public static void setTransactionTimeout(long timeOutInMillis)
{
conf.transaction_timeout = new DurationSpec.LongMillisecondsBound(timeOutInMillis);
}
public static void setCasContentionTimeout(long timeOutInMillis)
{
conf.cas_contention_timeout = new DurationSpec.LongMillisecondsBound(timeOutInMillis);
@ -2645,6 +2670,20 @@ public class DatabaseDescriptor
conf.concurrent_materialized_view_writes = concurrent_materialized_view_writes;
}
public static int getConcurrentAccordOps()
{
return conf.concurrent_accord_operations;
}
public static void setConcurrentAccordOps(int concurrent_operations)
{
if (concurrent_operations < 0)
{
throw new IllegalArgumentException("Concurrent accord operations must be non-negative");
}
conf.concurrent_accord_operations = concurrent_operations;
}
public static int getFlushWriters()
{
return conf.memtable_flush_writers;
@ -3545,6 +3584,11 @@ public class DatabaseDescriptor
return conf.paxos_topology_repair_strict_each_quorum;
}
public static Config.LegacyPaxosStrategy getLegacyPaxosStrategy()
{
return conf.legacy_paxos_strategy;
}
public static void setNativeTransportMaxRequestDataInFlightPerIpInBytes(long maxRequestDataInFlightInBytes)
{
if (maxRequestDataInFlightInBytes == -1)
@ -5144,6 +5188,16 @@ public class DatabaseDescriptor
}
}
public static boolean getAccordTransactionsEnabled()
{
return conf.accord_transactions_enabled;
}
public static void setAccordTransactionsEnabled(boolean b)
{
conf.accord_transactions_enabled = b;
}
public static boolean getForceNewPreparedStatementBehaviour()
{
return conf.force_new_prepared_statement_behaviour;

View File

@ -133,4 +133,14 @@ public interface CQLStatement
{
String keyspace();
}
interface CompositeCQLStatement extends CQLStatement
{
Iterable<? extends CQLStatement> getStatements();
}
interface ReturningCQLStatement extends CQLStatement
{
ResultSet.ResultMetadata getResultMetadata();
}
}

View File

@ -27,7 +27,15 @@ import org.apache.cassandra.cql3.terms.Sets;
import org.apache.cassandra.cql3.terms.Term;
import org.apache.cassandra.cql3.terms.UserTypes;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.marshal.*;
import org.apache.cassandra.db.marshal.CollectionType;
import org.apache.cassandra.db.marshal.CounterColumnType;
import org.apache.cassandra.db.marshal.ListType;
import org.apache.cassandra.db.marshal.MapType;
import org.apache.cassandra.db.marshal.NumberType;
import org.apache.cassandra.db.marshal.SetType;
import org.apache.cassandra.db.marshal.StringType;
import org.apache.cassandra.db.marshal.TupleType;
import org.apache.cassandra.db.marshal.UserType;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.TableMetadata;
@ -62,6 +70,11 @@ public abstract class Operation
this.t = t;
}
public Term term()
{
return t;
}
public void addFunctionsTo(List<Function> functions)
{
if (t != null)
@ -69,8 +82,7 @@ public abstract class Operation
}
/**
* @return whether the operation requires a read of the previous value to be executed
* (only lists setterByIdx, discard and discardByIdx requires that).
* @return whether the operation requires a read of the existing value to be executed
*/
public boolean requiresRead()
{
@ -178,7 +190,7 @@ public abstract class Operation
if (receiver.type.isCollection())
{
switch (((CollectionType) receiver.type).kind)
switch (((CollectionType<?>) receiver.type).kind)
{
case LIST:
return new Lists.Setter(receiver, v);
@ -228,7 +240,7 @@ public abstract class Operation
else if (!(receiver.type.isMultiCell()))
throw new InvalidRequestException(String.format("Invalid operation (%s) for frozen collection column %s", toString(receiver), receiver.name));
switch (((CollectionType)receiver.type).kind)
switch (((CollectionType<?>)receiver.type).kind)
{
case LIST:
Term idx = selector.prepare(metadata.keyspace, Lists.indexSpecOf(receiver));
@ -328,7 +340,7 @@ public abstract class Operation
else if (!(receiver.type.isMultiCell()))
throw new InvalidRequestException(String.format("Invalid operation (%s) for frozen collection column %s", toString(receiver), receiver.name));
switch (((CollectionType)receiver.type).kind)
switch (((CollectionType<?>)receiver.type).kind)
{
case LIST:
return new Lists.Appender(receiver, value.prepare(metadata.keyspace, receiver));
@ -389,7 +401,7 @@ public abstract class Operation
else if (!(receiver.type.isMultiCell()))
throw new InvalidRequestException(String.format("Invalid operation (%s) for frozen collection column %s", toString(receiver), receiver.name));
switch (((CollectionType)receiver.type).kind)
switch (((CollectionType<?>)receiver.type).kind)
{
case LIST:
return new Lists.Discarder(receiver, value.prepare(metadata.keyspace, receiver));
@ -400,7 +412,7 @@ public abstract class Operation
ColumnSpecification vr = new ColumnSpecification(receiver.ksName,
receiver.cfName,
receiver.name,
SetType.getInstance(((MapType)receiver.type).getKeysType(), false));
SetType.getInstance(((MapType<?, ?>) receiver.type).getKeysType(), true));
Term term;
try
{
@ -502,7 +514,7 @@ public abstract class Operation
else if (!(receiver.type.isMultiCell()))
throw new InvalidRequestException(String.format("Invalid deletion operation for frozen collection column %s", receiver.name));
switch (((CollectionType)receiver.type).kind)
switch (((CollectionType<?>)receiver.type).kind)
{
case LIST:
Term idx = element.prepare(keyspace, Lists.indexSpecOf(receiver));

View File

@ -21,10 +21,12 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import com.google.common.collect.Iterators;
import org.apache.cassandra.cql3.functions.Function;
import org.apache.cassandra.cql3.statements.StatementType;
import com.google.common.collect.Iterators;
import org.apache.cassandra.cql3.transactions.ReferenceOperation;
import org.apache.cassandra.schema.ColumnMetadata;
/**
* A set of <code>Operation</code>s.
@ -47,11 +49,34 @@ public final class Operations implements Iterable<Operation>
*/
private final List<Operation> staticOperations = new ArrayList<>();
private final List<ReferenceOperation> regularSubstitutions = new ArrayList<>();
private final List<ReferenceOperation> staticSubstitutions = new ArrayList<>();
public Operations(StatementType type)
{
this.type = type;
}
public void migrateReadRequiredOperations()
{
migrateReadRequiredOperations(staticOperations, staticSubstitutions);
migrateReadRequiredOperations(regularOperations, regularSubstitutions);
}
private static void migrateReadRequiredOperations(List<Operation> src, List<ReferenceOperation> dest)
{
Iterator<Operation> it = src.iterator();
while (it.hasNext())
{
Operation next = it.next();
if (next.requiresRead())
{
it.remove();
dest.add(ReferenceOperation.create(next));
}
}
}
/**
* Checks if some of the operations apply to static columns.
*
@ -105,6 +130,14 @@ public final class Operations implements Iterable<Operation>
regularOperations.add(operation);
}
public void add(ColumnMetadata column, ReferenceOperation operation)
{
if (column.isStatic())
staticSubstitutions.add(operation);
else
regularSubstitutions.add(operation);
}
/**
* Checks if one of the operations requires a read.
*
@ -143,4 +176,29 @@ public final class Operations implements Iterable<Operation>
regularOperations.forEach(p -> p.addFunctionsTo(functions));
staticOperations.forEach(p -> p.addFunctionsTo(functions));
}
public List<ReferenceOperation> allSubstitutions()
{
if (staticSubstitutions.isEmpty())
return regularSubstitutions;
if (regularSubstitutions.isEmpty())
return staticSubstitutions;
// Only create a new list if we actually have something to combine
List<ReferenceOperation> list = new ArrayList<>(staticSubstitutions.size() + regularSubstitutions.size());
list.addAll(staticSubstitutions);
list.addAll(regularSubstitutions);
return list;
}
public List<ReferenceOperation> regularSubstitutions()
{
return regularSubstitutions;
}
public List<ReferenceOperation> staticSubstitutions()
{
return staticSubstitutions;
}
}

View File

@ -26,6 +26,7 @@ import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.google.common.collect.RangeSet;
@ -39,13 +40,18 @@ import org.apache.cassandra.db.marshal.MultiElementType;
import org.apache.cassandra.db.marshal.SetType;
import org.apache.cassandra.db.rows.CellPath;
import org.apache.cassandra.db.rows.ComplexColumnData;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.serializers.ListSerializer;
import org.apache.cassandra.utils.ByteBufferUtil;
import static com.google.common.base.Preconditions.checkArgument;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkTrue;
import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest;
import static org.apache.cassandra.db.TypeSizes.sizeofUnsignedVInt;
public enum Operator
{
@ -828,6 +834,25 @@ public enum Operator
BINARY, TERNARY, MULTI_VALUE;
};
private static final Operator[] idToOperatorMapping;
static
{
Operator[] operators = values();
int maxId = Stream.of(operators)
.map(Operator::getValue)
.max(Integer::compareTo)
.get();
idToOperatorMapping = new Operator[maxId + 1];
for (Operator operator : operators)
{
if (null != idToOperatorMapping[operator.b])
throw new IllegalStateException("Duplicate Operator id " + operator.b);
idToOperatorMapping[operator.b] = operator;
}
}
/**
* The binary representation of this <code>Enum</code> value.
*/
@ -853,6 +878,17 @@ public enum Operator
output.writeInt(getValue());
}
/**
* Write the serialized version of this <code>Operator</code> to the specified output.
*
* @param output the output to write to
* @throws IOException if an I/O problem occurs while writing to the specified output
*/
public void writeToUnsignedVInt(DataOutputPlus output) throws IOException
{
output.writeUnsignedVInt32(b);
}
public int getValue()
{
return b;
@ -885,12 +921,27 @@ public enum Operator
*/
public static Operator readFrom(DataInput input) throws IOException
{
int b = input.readInt();
for (Operator operator : values())
if (operator.b == b)
return operator;
return fromBinary(input.readInt());
}
throw new IOException(String.format("Cannot resolve Relation.Type from binary representation: %s", b));
/**
* Deserializes a <code>Operator</code> instance from the specified input.
*
* @param input the input to read from
* @return the <code>Operator</code> instance deserialized
* @throws IOException if a problem occurs while deserializing the <code>Type</code> instance.
*/
public static Operator readFromUnsignedVInt(DataInputPlus input) throws IOException
{
return fromBinary(input.readUnsignedVInt32());
}
private static Operator fromBinary(int b) throws IOException
{
checkArgument(b > -1, "b must be > -1 to be a valid Operator id");
if (b > idToOperatorMapping.length)
throw new IOException(String.format("Cannot resolve Operator from binary representation: %s", b));
return idToOperatorMapping[b];
}
@ -1149,4 +1200,9 @@ public enum Operator
}
return String.format("%s %s %s", leftOperand, this, rightOperand);
}
public long sizeAsUnsignedVInt()
{
return sizeofUnsignedVInt(b);
}
}

View File

@ -53,6 +53,7 @@ import org.apache.cassandra.cql3.statements.BatchStatement;
import org.apache.cassandra.cql3.statements.ModificationStatement;
import org.apache.cassandra.cql3.statements.QualifiedStatement;
import org.apache.cassandra.cql3.statements.SelectStatement;
import org.apache.cassandra.cql3.statements.TransactionStatement;
import org.apache.cassandra.cql3.statements.schema.AlterSchemaStatement;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.DecoratedKey;
@ -297,6 +298,8 @@ public class QueryProcessor implements QueryHandler
return processNodeLocalWrite(statement, queryState, options);
else if (statement instanceof SelectStatement)
return processNodeLocalSelect((SelectStatement) statement, queryState, options);
else if (statement instanceof TransactionStatement)
return statement.executeLocally(queryState, options);
else
throw new InvalidRequestException("NODE_LOCAL consistency level can only be used with BATCH, UPDATE, INSERT, DELETE, and SELECT statements");
}
@ -1055,10 +1058,9 @@ public class QueryProcessor implements QueryHandler
statementKsName = selectStatement.keyspace();
statementCfName = selectStatement.table();
}
else if (statement instanceof BatchStatement)
else if (statement instanceof CQLStatement.CompositeCQLStatement)
{
BatchStatement batchStatement = ((BatchStatement) statement);
for (ModificationStatement stmt : batchStatement.getStatements())
for (CQLStatement stmt : ((CQLStatement.CompositeCQLStatement) statement).getStatements())
{
if (shouldInvalidate(ksName, cfName, stmt))
return true;

View File

@ -31,7 +31,6 @@ import java.util.Objects;
import com.google.common.annotations.VisibleForTesting;
import io.netty.buffer.ByteBuf;
import org.apache.cassandra.cql3.statements.SelectStatement;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.service.pager.PagingState;
import org.apache.cassandra.transport.CBCodec;
@ -314,8 +313,8 @@ public class ResultSet
public static ResultMetadata fromPrepared(CQLStatement statement)
{
if (statement instanceof SelectStatement)
return ((SelectStatement)statement).getResultMetadata();
if (statement instanceof CQLStatement.ReturningCQLStatement)
return ((CQLStatement.ReturningCQLStatement) statement).getResultMetadata();
return ResultSet.ResultMetadata.EMPTY;
}

View File

@ -37,7 +37,6 @@ import org.apache.cassandra.utils.TimeUUID;
public class UpdateParameters
{
public final TableMetadata metadata;
public final RegularAndStaticColumns updatedColumns;
public final ClientState clientState;
public final QueryOptions options;
@ -47,7 +46,7 @@ public class UpdateParameters
private final DeletionTime deletionTime;
// For lists operation that require a read-before-write. Will be null otherwise.
// Holds data for operations that require a read-before-write. Will be null otherwise.
private final Map<DecoratedKey, Partition> prefetchedRows;
private Row.Builder staticBuilder;
@ -57,17 +56,14 @@ public class UpdateParameters
private Row.Builder builder;
public UpdateParameters(TableMetadata metadata,
RegularAndStaticColumns updatedColumns,
ClientState clientState,
QueryOptions options,
long timestamp,
long nowInSec,
int ttl,
Map<DecoratedKey, Partition> prefetchedRows)
throws InvalidRequestException
Map<DecoratedKey, Partition> prefetchedRows) throws InvalidRequestException
{
this.metadata = metadata;
this.updatedColumns = updatedColumns;
this.clientState = clientState;
this.options = options;
@ -123,10 +119,20 @@ public class UpdateParameters
public void addPrimaryKeyLivenessInfo()
{
builder.addPrimaryKeyLivenessInfo(LivenessInfo.create(timestamp, ttl, nowInSec));
addPrimaryKeyLivenessInfo(LivenessInfo.create(timestamp, ttl, nowInSec));
}
private void addPrimaryKeyLivenessInfo(LivenessInfo info)
{
builder.addPrimaryKeyLivenessInfo(info);
}
public void addRowDeletion()
{
addRowDeletion(Row.Deletion.regular(deletionTime));
}
private void addRowDeletion(Row.Deletion deletion)
{
// For compact tables, at the exclusion of the static row (of static compact tables), each row ever has a single column,
// the "compact" one. As such, deleting the row or deleting that single cell is equivalent. We favor the later
@ -134,7 +140,7 @@ public class UpdateParameters
if (metadata.isCompactTable() && builder.clustering() != Clustering.STATIC_CLUSTERING)
addTombstone(((TableMetadata.CompactTableMetadata) metadata).compactValueColumn);
else
builder.addRowDeletion(Row.Deletion.regular(deletionTime));
builder.addRowDeletion(deletion);
}
public void addTombstone(ColumnMetadata column) throws InvalidRequestException
@ -179,6 +185,14 @@ public class UpdateParameters
return cell;
}
public void addRow(Row row)
{
newRow(row.clustering());
addRowDeletion(row.deletion());
addPrimaryKeyLivenessInfo(row.primaryKeyLivenessInfo());
row.cells().forEach(builder::addCell);
}
private void validateColumnSize(ColumnMetadata column, ByteBuffer value)
{
CQL3Type cql3Type = column.type.asCQL3Type();

View File

@ -17,6 +17,7 @@
*/
package org.apache.cassandra.cql3.conditions;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*;
@ -42,11 +43,17 @@ import org.apache.cassandra.db.rows.Cell;
import org.apache.cassandra.db.rows.ColumnData;
import org.apache.cassandra.db.rows.ComplexColumnData;
import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.io.IVersionedSerializer;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.utils.ByteBufferUtil;
import static org.apache.cassandra.cql3.statements.RequestValidations.*;
import static org.apache.cassandra.db.TypeSizes.sizeofUnsignedVInt;
import static org.apache.cassandra.service.accord.AccordSerializers.columnMetadataSerializer;
import static org.apache.cassandra.utils.ByteBufferUtil.nullableByteBufferSerializer;
/**
* A CQL3 condition on the value of a column or collection element. For example, "UPDATE .. IF a = 0".
@ -171,6 +178,41 @@ public final class ColumnCondition
return operator.buildCQLString(columnsExpression, values);
}
private interface BoundSerializer<T extends Bound>
{
default void serialize(T bound, DataOutputPlus out, int version) throws IOException {}
Bound deserialize(DataInputPlus in, int version, ColumnMetadata column, Operator operator, ByteBuffer value) throws IOException;
default long serializedSize(T condition, int version) { return 0; }
}
enum BoundKind
{
Simple(0, SimpleBound.serializer),
ElementOrFieldAccess(1, ElementOrFieldAccessBound.serializer),
MultiCell(2, MultiCellBound.serializer);
private final int id;
@SuppressWarnings("rawtypes")
private final BoundSerializer serializer;
BoundKind(int id, BoundSerializer<?> serializer)
{
this.id = id;
this.serializer = serializer;
}
static BoundKind valueOf(int id)
{
switch (id)
{
case 0: return BoundKind.Simple;
case 1: return BoundKind.ElementOrFieldAccess;
case 2: return BoundKind.MultiCell;
default: throw new IllegalArgumentException("Unknown id: " + id);
}
}
}
public static abstract class Bound
{
protected final ColumnMetadata column;
@ -188,14 +230,55 @@ public final class ColumnCondition
* Validates whether this condition applies to {@code current}.
*/
public abstract boolean appliesTo(Row row);
protected abstract BoundKind kind();
public static final IVersionedSerializer<Bound> serializer = new IVersionedSerializer<>()
{
@Override
@SuppressWarnings("unchecked")
public void serialize(Bound bound, DataOutputPlus out, int version) throws IOException
{
columnMetadataSerializer.serialize(bound.column, out, version);
bound.operator.writeToUnsignedVInt(out);
nullableByteBufferSerializer.serialize(bound.value, out, version);
BoundKind kind = bound.kind();
out.writeUnsignedVInt32(kind.ordinal());
kind.serializer.serialize(bound, out, version);
}
@Override
public Bound deserialize(DataInputPlus in, int version) throws IOException
{
ColumnMetadata column = columnMetadataSerializer.deserialize(in, version);
Operator operator = Operator.readFromUnsignedVInt(in);
ByteBuffer value = nullableByteBufferSerializer.deserialize(in, version);
BoundKind boundKind = BoundKind.valueOf(in.readUnsignedVInt32());
return boundKind.serializer.deserialize(in, version, column, operator, value);
}
@Override
@SuppressWarnings("unchecked")
public long serializedSize(Bound bound, int version)
{
BoundKind kind = bound.kind();
return columnMetadataSerializer.serializedSize(bound.column, version)
+ bound.operator.sizeAsUnsignedVInt()
+ nullableByteBufferSerializer.serializedSize(bound.value, version)
+ sizeofUnsignedVInt(kind.ordinal())
+ kind.serializer.serializedSize(bound, version);
}
};
}
/**
* A condition on a single non-collection column.
*/
private static final class SimpleBound extends Bound
public static class SimpleBound extends Bound
{
private SimpleBound(ColumnMetadata column, Operator operator, ByteBuffer value)
private static final BoundSerializer<SimpleBound> serializer = (in, version, column, operator, value) -> new SimpleBound(column, operator, value);
public SimpleBound(ColumnMetadata column, Operator operator, ByteBuffer value)
{
super(column, operator, value);
}
@ -206,7 +289,7 @@ public final class ColumnCondition
return operator.isSatisfiedBy(column.type, rowValue(row), value);
}
private ByteBuffer rowValue(Row row)
protected ByteBuffer rowValue(Row row)
{
// If we're asking for a given cell, and we didn't get any row from our read, it's
// the same as not having said cell.
@ -216,13 +299,70 @@ public final class ColumnCondition
Cell<?> c = row.getCell(column);
return c == null ? null : c.buffer();
}
@Override
protected BoundKind kind()
{
return BoundKind.Simple;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SimpleBound bound = (SimpleBound) o;
return column.equals(bound.column) && operator == bound.operator && Objects.equals(value, bound.value);
}
@Override
public int hashCode()
{
return Objects.hash(column, operator, value);
}
}
public static class SimpleClusteringBound extends SimpleBound
{
public SimpleClusteringBound(ColumnMetadata column, Operator operator, ByteBuffer value)
{
super(column, operator, value);
assert column.isClusteringColumn() : String.format("Column must be a clustering column, but given %s", column);
}
@Override
protected ByteBuffer rowValue(Row row)
{
return row == null ? null : row.clustering().bufferAt(column.position());
}
}
/**
* A condition on a collection element or a UDT field.
*/
private static final class ElementOrFieldAccessBound extends Bound
public static final class ElementOrFieldAccessBound extends Bound
{
private static final BoundSerializer<ElementOrFieldAccessBound> serializer = new BoundSerializer<>()
{
@Override
public void serialize(ElementOrFieldAccessBound bound, DataOutputPlus out, int version) throws IOException
{
nullableByteBufferSerializer.serialize(bound.keyOrIndex, out, version);
}
@Override
public Bound deserialize(DataInputPlus in, int version, ColumnMetadata column, Operator operator, ByteBuffer value) throws IOException
{
ByteBuffer keyOrIndex = nullableByteBufferSerializer.deserialize(in, version);
return new ElementOrFieldAccessBound(column, keyOrIndex, operator, value);
}
@Override
public long serializedSize(ElementOrFieldAccessBound condition, int version)
{
return nullableByteBufferSerializer.serializedSize(condition.keyOrIndex, version);
}
};
/**
* The collection element or UDT field type.
*/
@ -234,16 +374,22 @@ public final class ColumnCondition
private final ByteBuffer keyOrIndex;
private ElementOrFieldAccessBound(ColumnMetadata column,
ByteBuffer keyOrIndex,
Operator operator,
ByteBuffer value)
public ElementOrFieldAccessBound(ColumnMetadata column,
ByteBuffer keyOrIndex,
Operator operator,
ByteBuffer value)
{
super(column, operator, value);
this.elementType = ((MultiElementType<?>) column.type).elementType(keyOrIndex);
this.keyOrIndex = keyOrIndex;
}
@Override
protected BoundKind kind()
{
return BoundKind.ElementOrFieldAccess;
}
@Override
public boolean appliesTo(Row row)
{
@ -260,17 +406,40 @@ public final class ColumnCondition
{
return row == null ? null : row.getColumnData(column);
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ElementOrFieldAccessBound bound = (ElementOrFieldAccessBound) o;
return column.equals(bound.column) && operator == bound.operator && Objects.equals(value, bound.value) && Objects.equals(keyOrIndex, bound.keyOrIndex);
}
@Override
public int hashCode()
{
return Objects.hash(column, operator, value);
}
}
/**
* A condition on a multicell column.
*/
private static final class MultiCellBound extends Bound
public static final class MultiCellBound extends Bound
{
private static final BoundSerializer<MultiCellBound> serializer = (in, version, column, operator, value) -> new MultiCellBound(column, operator, value);
public MultiCellBound(ColumnMetadata column, Operator operator, ByteBuffer value)
{
super(column, operator, value);
assert column.type.isMultiCell();
assert column.type.isMultiCell() : String.format("Unexpected type: %s", column.type);
}
@Override
protected BoundKind kind()
{
return BoundKind.MultiCell;
}
public boolean appliesTo(Row row)
@ -278,6 +447,21 @@ public final class ColumnCondition
ComplexColumnData columnData = row == null ? null : row.getComplexColumnData(column);
return operator.isSatisfiedBy((MultiElementType<?>) column.type, columnData, value);
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MultiCellBound bound = (MultiCellBound) o;
return column.equals(bound.column) && operator == bound.operator && Objects.equals(value, bound.value);
}
@Override
public int hashCode()
{
return Objects.hash(column, operator, value);
}
}
public static class Raw

View File

@ -868,13 +868,23 @@ public final class StatementRestrictions
*
* @return <code>true</code> if all the primary key columns are restricted by an equality relation.
*/
public boolean hasAllPKColumnsRestrictedByEqualities()
public boolean hasAllPrimaryKeyColumnsRestrictedByEqualities()
{
return hasAllPartitionKeyColumnsRestrictedByEqualities()
&& !hasUnrestrictedClusteringColumns()
&& (clusteringColumnsRestrictions.hasOnlyEqualityRestrictions());
}
/**
* Checks that all the partition key columns are restricted by an equality relation ('=' or 'IN').
*
* @return <code>true</code> if all the partition key columns are restricted by an equality relation.
*/
public boolean hasAllPartitionKeyColumnsRestrictedByEqualities()
{
return !isPartitionKeyRestrictionsOnToken()
&& !partitionKeyRestrictions.hasUnrestrictedPartitionKeyComponents()
&& (partitionKeyRestrictions.hasOnlyEqualityRestrictions())
&& !hasUnrestrictedClusteringColumns()
&& (clusteringColumnsRestrictions.hasOnlyEqualityRestrictions());
&& !partitionKeyRestrictions.hasUnrestrictedPartitionKeyComponents()
&& (partitionKeyRestrictions.hasOnlyEqualityRestrictions());
}
/**

View File

@ -577,8 +577,8 @@ public interface Selectable extends AssignmentTestable
public static class Raw implements Selectable.Raw
{
private final Selectable.Raw selected;
private final FieldIdentifier field;
public final Selectable.Raw selected;
public final FieldIdentifier field;
public Raw(Selectable.Raw selected, FieldIdentifier field)
{
@ -1471,8 +1471,8 @@ public interface Selectable extends AssignmentTestable
public static class Raw implements Selectable.Raw
{
private final Selectable.Raw selected;
private final Term.Raw element;
public final Selectable.Raw selected;
public final Term.Raw element;
public Raw(Selectable.Raw selected, Term.Raw element)
{

View File

@ -18,7 +18,12 @@
package org.apache.cassandra.cql3.selection;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import com.google.common.base.MoreObjects;
import com.google.common.base.Predicate;
@ -134,6 +139,11 @@ public abstract class Selection
return resultMetadata;
}
public static Selection.Selectors noopSelector()
{
return new SimpleSelectors();
}
public static Selection wildcard(TableMetadata table, boolean isJson, boolean returnStaticContentOnPartitionWithNoRows)
{
List<ColumnMetadata> all = new ArrayList<>(table.columns().size());
@ -346,55 +356,72 @@ public abstract class Selection
return Arrays.asList(jsonRow);
}
public static interface Selectors
public interface Selectors
{
/**
* Returns the {@code ColumnFilter} corresponding to those selectors
*
* @return the {@code ColumnFilter} corresponding to those selectors
*/
public ColumnFilter getColumnFilter();
default ColumnFilter getColumnFilter() { return ColumnFilter.NONE; }
/**
* Checks if this Selectors perform some processing
* @return {@code true} if this Selectors perform some processing, {@code false} otherwise.
*/
public boolean hasProcessing();
default boolean hasProcessing() { return false; }
/**
* Checks if one of the selectors perform some aggregations.
* @return {@code true} if one of the selectors perform some aggregations, {@code false} otherwise.
*/
public boolean isAggregate();
/**
* Returns the number of fetched columns
* @return the number of fetched columns
*/
public int numberOfFetchedColumns();
default boolean isAggregate() { return false; }
/**
* Checks if one of the selectors collect TTLs.
* @return {@code true} if one of the selectors collect TTLs, {@code false} otherwise.
*/
public boolean collectTTLs();
default boolean collectTTLs() { return false; }
/**
* Checks if one of the selectors collects write timestamps.
* @return {@code true} if one of the selectors collects write timestamps, {@code false} otherwise.
*/
public boolean collectWritetimes();
default boolean collectWritetimes() { return false; }
/**
* Adds the current row of the specified <code>ResultSetBuilder</code>.
*
* @param input the input row
*/
public void addInputRow(InputRow input);
void addInputRow(InputRow input);
public List<ByteBuffer> getOutputRow();
List<ByteBuffer> getOutputRow();
public void reset();
void reset();
}
public static class SimpleSelectors implements Selectors
{
protected List<ByteBuffer> current;
@Override
public void addInputRow(InputRow input)
{
current = input.getValues();
}
@Override
public List<ByteBuffer> getOutputRow()
{
return current;
}
@Override
public void reset()
{
current = null;
}
}
// Special cased selection for when only columns are selected.
@ -466,15 +493,9 @@ public abstract class Selection
public Selectors newSelectors(QueryOptions options)
{
return new Selectors()
return new SimpleSelectors()
{
private List<ByteBuffer> current;
public void reset()
{
current = null;
}
@Override
public List<ByteBuffer> getOutputRow()
{
if (isJson)
@ -482,39 +503,6 @@ public abstract class Selection
return current;
}
public void addInputRow(InputRow input)
{
current = input.getValues();
}
public boolean isAggregate()
{
return false;
}
public boolean hasProcessing()
{
return false;
}
@Override
public int numberOfFetchedColumns()
{
return getColumns().size();
}
@Override
public boolean collectTTLs()
{
return false;
}
@Override
public boolean collectWritetimes()
{
return false;
}
@Override
public ColumnFilter getColumnFilter()
{
@ -615,12 +603,6 @@ public abstract class Selection
selector.addInput(input);
}
@Override
public int numberOfFetchedColumns()
{
return getColumns().size();
}
@Override
public boolean collectTTLs()
{

View File

@ -414,7 +414,7 @@ public abstract class Selector
UserType udt = (UserType) type;
int size = udt.size();
values[index] = udt.serializeForNativeProtocol(ccd.iterator(), protocolVersion);
values[index] = udt.serializeForNativeProtocol(ccd.iterator());
short fieldPosition = 0;
for (Cell<?> cell : ccd)

View File

@ -59,7 +59,7 @@ import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse
/**
* A <code>BATCH</code> statement parsed from a CQL query.
*/
public class BatchStatement implements CQLStatement
public class BatchStatement implements CQLStatement.CompositeCQLStatement
{
public enum Type
{
@ -268,6 +268,7 @@ public class BatchStatement implements CQLStatement
statement.validate(state);
}
@Override
public List<ModificationStatement> getStatements()
{
return statements;
@ -617,7 +618,7 @@ public class BatchStatement implements CQLStatement
return String.format("BatchStatement(type=%s, statements=%s)", type, statements);
}
public static class Parsed extends QualifiedStatement
public static class Parsed extends QualifiedStatement.Composite
{
private final Type type;
private final Attributes.Raw attrs;
@ -625,21 +626,15 @@ public class BatchStatement implements CQLStatement
public Parsed(Type type, Attributes.Raw attrs, List<ModificationStatement.Parsed> parsedStatements)
{
super(null);
this.type = type;
this.attrs = attrs;
this.parsedStatements = parsedStatements;
}
// Not doing this in the constructor since we only need this for prepared statements
@Override
public boolean isFullyQualified()
protected Iterable<? extends QualifiedStatement> getStatements()
{
for (ModificationStatement.Parsed statement : parsedStatements)
if (!statement.isFullyQualified())
return false;
return true;
return parsedStatements;
}
@Override

View File

@ -17,28 +17,60 @@
*/
package org.apache.cassandra.cql3.statements;
import java.util.*;
import org.apache.cassandra.db.marshal.TimeUUIDType;
import org.apache.cassandra.index.IndexRegistry;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.cql3.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import accord.api.Update;
import accord.primitives.Txn;
import org.apache.cassandra.cql3.QueryOptions;
import org.apache.cassandra.cql3.UpdateParameters;
import org.apache.cassandra.cql3.conditions.ColumnCondition;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.filter.*;
import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.db.Clustering;
import org.apache.cassandra.db.Columns;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.RegularAndStaticColumns;
import org.apache.cassandra.db.SinglePartitionReadCommand;
import org.apache.cassandra.db.Slice;
import org.apache.cassandra.db.Slices;
import org.apache.cassandra.db.filter.ClusteringIndexNamesFilter;
import org.apache.cassandra.db.filter.ClusteringIndexSliceFilter;
import org.apache.cassandra.db.filter.ColumnFilter;
import org.apache.cassandra.db.filter.DataLimits;
import org.apache.cassandra.db.filter.RowFilter;
import org.apache.cassandra.db.partitions.FilteredPartition;
import org.apache.cassandra.db.partitions.Partition;
import org.apache.cassandra.db.partitions.PartitionUpdate;
import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.db.rows.RowIterator;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.service.CASRequest;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.accord.txn.TxnCondition;
import org.apache.cassandra.service.accord.txn.TxnData;
import org.apache.cassandra.service.accord.txn.TxnDataName;
import org.apache.cassandra.service.accord.txn.TxnQuery;
import org.apache.cassandra.service.accord.txn.TxnRead;
import org.apache.cassandra.service.accord.txn.TxnReference;
import org.apache.cassandra.service.accord.txn.TxnUpdate;
import org.apache.cassandra.service.accord.txn.TxnWrite;
import org.apache.cassandra.service.paxos.Ballot;
import org.apache.cassandra.transport.Dispatcher;
import org.apache.cassandra.utils.TimeUUID;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import static com.google.common.base.Preconditions.checkState;
import static org.apache.cassandra.service.accord.txn.TxnDataName.Kind.USER;
/**
* Processed CAS conditions and update on potentially multiple rows of the same partition.
@ -259,9 +291,9 @@ public class CQL3CasRequest implements CASRequest
final long timeUuidMsb;
long timeUuidNanos;
public CASUpdateParameters(TableMetadata metadata, RegularAndStaticColumns updatedColumns, ClientState state, QueryOptions options, long timestamp, long nowInSec, int ttl, Map<DecoratedKey, Partition> prefetchedRows, long timeUuidMsb, long timeUuidNanos) throws InvalidRequestException
public CASUpdateParameters(TableMetadata metadata, ClientState state, QueryOptions options, long timestamp, long nowInSec, int ttl, Map<DecoratedKey, Partition> prefetchedRows, long timeUuidMsb, long timeUuidNanos) throws InvalidRequestException
{
super(metadata, updatedColumns, state, options, timestamp, nowInSec, ttl, prefetchedRows);
super(metadata, state, options, timestamp, nowInSec, ttl, prefetchedRows);
this.timeUuidMsb = timeUuidMsb;
this.timeUuidNanos = timeUuidNanos;
}
@ -299,7 +331,7 @@ public class CQL3CasRequest implements CASRequest
{
Map<DecoratedKey, Partition> map = stmt.requiresRead() ? Collections.singletonMap(key, current) : null;
CASUpdateParameters params =
new CASUpdateParameters(metadata, updateBuilder.columns(), state, options, timestamp, nowInSeconds,
new CASUpdateParameters(metadata, state, options, timestamp, nowInSeconds,
stmt.getTimeToLive(options), map, timeUuidMsb, timeUuidNanos);
stmt.addUpdateForKey(updateBuilder, clustering, params);
return params.timeUuidNanos;
@ -329,7 +361,6 @@ public class CQL3CasRequest implements CASRequest
Map<DecoratedKey, Partition> map = stmt.requiresRead() ? Collections.singletonMap(key, current) : null;
UpdateParameters params =
new UpdateParameters(metadata,
updateBuilder.columns(),
state,
options,
timestamp,
@ -350,6 +381,8 @@ public class CQL3CasRequest implements CASRequest
}
public abstract boolean appliesTo(FilteredPartition current) throws InvalidRequestException;
public abstract TxnCondition asTxnCondition();
}
private interface ToCQL
@ -374,6 +407,13 @@ public class CQL3CasRequest implements CASRequest
{
return "IF NOT EXISTS";
}
public TxnCondition asTxnCondition()
{
TxnDataName txnDataName = new TxnDataName(USER, clustering, TxnRead.SERIAL_READ_NAME);
TxnReference txnReference = new TxnReference(txnDataName, null);
return new TxnCondition.Exists(txnReference, TxnCondition.Kind.IS_NULL);
}
}
private static class ExistCondition extends RowCondition implements ToCQL
@ -393,6 +433,13 @@ public class CQL3CasRequest implements CASRequest
{
return "IF EXISTS";
}
public TxnCondition asTxnCondition()
{
TxnDataName txnDataName = new TxnDataName(USER, clustering, TxnRead.SERIAL_READ_NAME);
TxnReference txnReference = new TxnReference(txnDataName, null);
return new TxnCondition.Exists(txnReference, TxnCondition.Kind.IS_NOT_NULL);
}
}
private static class ColumnsConditions extends RowCondition
@ -422,6 +469,12 @@ public class CQL3CasRequest implements CASRequest
}
return true;
}
@Override
public TxnCondition asTxnCondition()
{
return new TxnCondition.ColumnConditionsAdapter(clustering, conditions);
}
}
@Override
@ -429,4 +482,56 @@ public class CQL3CasRequest implements CASRequest
{
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
@Override
public Txn toAccordTxn(ClientState clientState, long nowInSecs)
{
SinglePartitionReadCommand readCommand = readCommand(nowInSecs);
Update update = createUpdate(clientState);
// In a CAS request only one key is supported and writes
// can't be dependent on any data that is read (only conditions)
// so the only relevant keys are the read key
TxnRead read = TxnRead.createSerialRead(readCommand);
return new Txn.InMemory(read.keys(), read, TxnQuery.CONDITION, update);
}
private Update createUpdate(ClientState clientState)
{
return new TxnUpdate(createWriteFragments(clientState), createCondition());
}
private TxnCondition createCondition()
{
List<TxnCondition> txnConditions = new ArrayList<>(conditions.size() + (staticConditions == null ? 0 : 1));
if (staticConditions != null)
{
txnConditions.add(staticConditions.asTxnCondition());
}
for (RowCondition condition : conditions.values())
txnConditions.add(condition.asTxnCondition());
// CAS forbids empty conditions
checkState(!txnConditions.isEmpty());
return conditions.size() == 1 ? txnConditions.get(0) : new TxnCondition.BooleanGroup(TxnCondition.Kind.AND, txnConditions);
}
private List<TxnWrite.Fragment> createWriteFragments(ClientState state)
{
List<TxnWrite.Fragment> fragments = new ArrayList<>();
int idx = 0;
for (RowUpdate update : updates)
{
ModificationStatement modification = update.stmt;
QueryOptions options = update.options;
TxnWrite.Fragment fragment = modification.getTxnWriteFragment(idx++, state, options);
fragments.add(fragment);
}
return fragments;
}
@Override
public RowIterator toCasResult(TxnData txnData)
{
FilteredPartition partition = txnData.get(TxnRead.SERIAL_READ);
return partition != null ? partition.rowIterator() : null;
}
}

View File

@ -177,7 +177,7 @@ public class DeleteStatement extends ModificationStatement
conditions,
attrs);
if (stmt.hasConditions() && !restrictions.hasAllPKColumnsRestrictedByEqualities())
if (stmt.hasConditions() && !restrictions.hasAllPrimaryKeyColumnsRestrictedByEqualities())
{
checkFalse(stmt.isVirtual(), "DELETE statements must restrict all PRIMARY KEY columns with equality relations");

View File

@ -18,15 +18,45 @@
package org.apache.cassandra.cql3.statements;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Set;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.auth.Permission;
import org.apache.cassandra.cql3.Attributes;
import org.apache.cassandra.cql3.CQLStatement;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.ColumnSpecification;
import org.apache.cassandra.cql3.Ordering;
import org.apache.cassandra.cql3.terms.Constants;
import org.apache.cassandra.cql3.Operation;
import org.apache.cassandra.cql3.Operations;
import org.apache.cassandra.cql3.QualifiedName;
import org.apache.cassandra.cql3.QueryOptions;
import org.apache.cassandra.cql3.QueryProcessor;
import org.apache.cassandra.cql3.ResultSet;
import org.apache.cassandra.cql3.UpdateParameters;
import org.apache.cassandra.cql3.Validation;
import org.apache.cassandra.cql3.VariableSpecifications;
import org.apache.cassandra.cql3.WhereClause;
import org.apache.cassandra.cql3.constraints.ConstraintViolationException;
import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.dht.Token;
@ -37,7 +67,6 @@ import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.schema.ViewMetadata;
import org.apache.cassandra.cql3.*;
import org.apache.cassandra.cql3.conditions.ColumnCondition;
import org.apache.cassandra.cql3.conditions.ColumnConditions;
import org.apache.cassandra.cql3.conditions.Conditions;
@ -46,6 +75,7 @@ import org.apache.cassandra.cql3.restrictions.StatementRestrictions;
import org.apache.cassandra.cql3.selection.ResultSetBuilder;
import org.apache.cassandra.cql3.selection.Selection;
import org.apache.cassandra.cql3.selection.Selection.Selectors;
import org.apache.cassandra.cql3.transactions.ReferenceOperation;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.filter.*;
import org.apache.cassandra.db.marshal.BooleanType;
@ -62,8 +92,12 @@ import org.apache.cassandra.service.paxos.Ballot;
import org.apache.cassandra.service.paxos.BallotGenerator;
import org.apache.cassandra.service.paxos.Commit.Proposal;
import org.apache.cassandra.transport.Dispatcher;
import org.apache.cassandra.service.accord.txn.TxnReferenceOperation;
import org.apache.cassandra.service.accord.txn.TxnReferenceOperations;
import org.apache.cassandra.service.accord.txn.TxnWrite;
import org.apache.cassandra.transport.messages.ResultMessage;
import org.apache.cassandra.triggers.TriggerExecutor;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.MD5Digest;
@ -146,6 +180,15 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa
requiresReadBuilder.add(operation.column);
}
}
for (ReferenceOperation operation : operations.allSubstitutions())
{
ColumnMetadata receiver = operation.getReceiver();
updatedColumnsBuilder.add(receiver);
// If the operation requires a read-before-write, make sure its receiver is selected by the auto-read the
// transaction creates during update creation. (see createSelectForTxn())
if (operation.requiresRead())
requiresReadBuilder.add(receiver);
}
RegularAndStaticColumns modifiedColumns = updatedColumnsBuilder.build();
@ -389,6 +432,11 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa
return operations.staticOperations();
}
public Collection<ReferenceOperation> allReferenceOperations()
{
return operations.allSubstitutions();
}
public Iterable<ColumnMetadata> getColumnsWithConditions()
{
return conditions.getColumns();
@ -451,7 +499,7 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa
// * Deleting list element by value
// * Performing addition on a StringType (i.e. concatenation, only supported for CAS operations)
// * Performing addition on a NumberType, again only supported for CAS operations.
return !requiresRead.isEmpty();
return operations.requiresRead();
}
private Map<DecoratedKey, Partition> readRequiredLists(Collection<ByteBuffer> partitionKeys,
@ -775,7 +823,7 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa
*
* @return list of the mutations
*/
private List<? extends IMutation> getMutations(ClientState state,
public List<? extends IMutation> getMutations(ClientState state,
QueryOptions options,
boolean local,
long timestamp,
@ -797,6 +845,56 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa
}
}
@VisibleForTesting
public PartitionUpdate getTxnUpdate(ClientState state, QueryOptions options)
{
List<? extends IMutation> mutations = getMutations(state, options, false, 0, 0, new Dispatcher.RequestTime(0, 0));
if (mutations.size() != 1)
throw new IllegalArgumentException("When running withing a transaction, modification statements may only mutate a single partition");
return Iterables.getOnlyElement(mutations.get(0).getPartitionUpdates());
}
private static List<TxnReferenceOperation> getTxnReferenceOps(List<ReferenceOperation> operations, QueryOptions options)
{
if (operations.isEmpty())
return Collections.emptyList();
List<TxnReferenceOperation> result = new ArrayList<>(operations.size());
for (ReferenceOperation operation : operations)
result.add(operation.bindAndGet(options));
return result;
}
public TxnReferenceOperations getTxnReferenceOps(QueryOptions options, ClientState state)
{
List<TxnReferenceOperation> regularOps = getTxnReferenceOps(operations.regularSubstitutions(), options);
List<TxnReferenceOperation> staticOps = getTxnReferenceOps(operations.staticSubstitutions(), options);
Clustering<?> clustering = !regularOps.isEmpty() ? Iterables.getOnlyElement(createClustering(options, state)) : null;
return new TxnReferenceOperations(metadata, clustering, regularOps, staticOps);
}
@VisibleForTesting
public void migrateReadRequiredOperations()
{
operations.migrateReadRequiredOperations();
}
@VisibleForTesting
public List<ReferenceOperation> getSubstitutions()
{
return operations.allSubstitutions();
}
public TxnWrite.Fragment getTxnWriteFragment(int index, ClientState state, QueryOptions options)
{
// When an Operation requires a read, this cannot be done right away and must be done by the transaction itself,
// so migrate those Operations to a ReferenceOperation (which works properly in this case).
operations.migrateReadRequiredOperations();
PartitionUpdate baseUpdate = getTxnUpdate(state, options);
TxnReferenceOperations referenceOps = getTxnReferenceOps(options, state);
return new TxnWrite.Fragment(index, baseUpdate, referenceOps);
}
final void addUpdates(UpdatesCollector collector,
List<ByteBuffer> keys,
ClientState state,
@ -949,7 +1047,6 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa
requestTime);
return new UpdateParameters(metadata(),
updatedColumns(),
state,
options,
getTimestamp(timestamp, options),
@ -995,6 +1092,7 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa
Conditions preparedConditions = prepareConditions(metadata, bindVariables);
// TODO: if this is a txn and has a read name, and updates non-static columns, confirm it selects an entire row
return prepareInternal(state, metadata, bindVariables, preparedConditions, preparedAttributes);
}
@ -1019,7 +1117,6 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa
if (ifNotExists)
{
assert conditions.isEmpty();
assert !ifExists;
return Conditions.IF_NOT_EXISTS_CONDITION;
}
@ -1088,4 +1185,23 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa
return conditions;
}
}
private static final Constants.Value ONE = new Constants.Value(ByteBufferUtil.bytes(1));
public SelectStatement createSelectForTxn()
{
// TODO: get working with static-only updates that don't specify any/all primary key columns
Preconditions.checkState(getRestrictions().hasAllPrimaryKeyColumnsRestrictedByEqualities());
Selection selection = Selection.forColumns(metadata, Lists.newArrayList(requiresRead), false);
return new SelectStatement(metadata,
bindVariables,
SelectStatement.defaultParameters,
selection,
getRestrictions(),
false,
null,
null,
ONE,
null);
}
}

View File

@ -78,4 +78,50 @@ public abstract class QualifiedStatement extends CQLStatement.Raw
{
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
public static abstract class Composite extends QualifiedStatement
{
Composite()
{
super(null);
}
protected abstract Iterable<? extends QualifiedStatement> getStatements();
@Override
public boolean isFullyQualified()
{
for (QualifiedStatement statement : getStatements())
if (!statement.isFullyQualified())
return false;
return true;
}
@Override
public void setKeyspace(ClientState state)
{
for (QualifiedStatement statement : getStatements())
statement.setKeyspace(state);
}
@Override
public void setKeyspace(String keyspace)
{
for (QualifiedStatement statement : getStatements())
statement.setKeyspace(keyspace);
}
@Override
public String keyspace()
{
return null;
}
@Override
public String name()
{
return null;
}
}
}

View File

@ -18,10 +18,20 @@
package org.apache.cassandra.cql3.statements;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.stream.Collectors;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.annotation.concurrent.ThreadSafe;
import com.google.common.annotations.VisibleForTesting;
@ -30,24 +40,26 @@ import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.audit.AuditLogContext;
import org.apache.cassandra.audit.AuditLogEntryType;
import org.apache.cassandra.auth.Permission;
import org.apache.cassandra.cql3.restrictions.SingleRestriction;
import org.apache.cassandra.cql3.terms.Term;
import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.index.Index;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.schema.TableMetadataRef;
import org.apache.cassandra.cql3.*;
import org.apache.cassandra.cql3.CQLStatement;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.ColumnSpecification;
import org.apache.cassandra.cql3.Ordering;
import org.apache.cassandra.cql3.QualifiedName;
import org.apache.cassandra.cql3.QueryOptions;
import org.apache.cassandra.cql3.QueryProcessor;
import org.apache.cassandra.cql3.ResultSet;
import org.apache.cassandra.cql3.VariableSpecifications;
import org.apache.cassandra.cql3.WhereClause;
import org.apache.cassandra.cql3.functions.Function;
import org.apache.cassandra.cql3.restrictions.SingleRestriction;
import org.apache.cassandra.cql3.restrictions.StatementRestrictions;
import org.apache.cassandra.cql3.selection.RawSelector;
import org.apache.cassandra.cql3.selection.ResultSetBuilder;
@ -56,10 +68,31 @@ import org.apache.cassandra.cql3.selection.Selectable.WithFunction;
import org.apache.cassandra.cql3.selection.Selection;
import org.apache.cassandra.cql3.selection.Selection.Selectors;
import org.apache.cassandra.cql3.selection.Selector;
import org.apache.cassandra.db.*;
import org.apache.cassandra.cql3.terms.Marker;
import org.apache.cassandra.cql3.terms.Term;
import org.apache.cassandra.db.Clustering;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.DataRange;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.PartitionPosition;
import org.apache.cassandra.db.PartitionRangeReadQuery;
import org.apache.cassandra.db.ReadExecutionController;
import org.apache.cassandra.db.ReadQuery;
import org.apache.cassandra.db.SinglePartitionReadCommand;
import org.apache.cassandra.db.SinglePartitionReadQuery;
import org.apache.cassandra.db.Slice;
import org.apache.cassandra.db.Slices;
import org.apache.cassandra.db.aggregation.AggregationSpecification;
import org.apache.cassandra.db.aggregation.GroupMaker;
import org.apache.cassandra.db.filter.*;
import org.apache.cassandra.db.filter.ClusteringIndexFilter;
import org.apache.cassandra.db.filter.ClusteringIndexNamesFilter;
import org.apache.cassandra.db.filter.ClusteringIndexSliceFilter;
import org.apache.cassandra.db.filter.ColumnFilter;
import org.apache.cassandra.db.filter.DataLimits;
import org.apache.cassandra.db.filter.RowFilter;
import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.db.marshal.CompositeType;
import org.apache.cassandra.db.marshal.Int32Type;
import org.apache.cassandra.db.partitions.PartitionIterator;
@ -67,9 +100,20 @@ import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.db.rows.RowIterator;
import org.apache.cassandra.db.view.View;
import org.apache.cassandra.dht.AbstractBounds;
import org.apache.cassandra.exceptions.*;
import org.apache.cassandra.metrics.ClientRequestSizeMetrics;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.exceptions.ReadSizeAbortException;
import org.apache.cassandra.exceptions.RequestExecutionException;
import org.apache.cassandra.exceptions.RequestFailureReason;
import org.apache.cassandra.exceptions.RequestValidationException;
import org.apache.cassandra.exceptions.UnauthorizedException;
import org.apache.cassandra.index.Index;
import org.apache.cassandra.index.IndexRegistry;
import org.apache.cassandra.metrics.ClientRequestSizeMetrics;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.schema.TableMetadataRef;
import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.ClientWarn;
@ -85,9 +129,6 @@ import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.NoSpamLogger;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import static java.lang.String.format;
import static org.apache.cassandra.cql3.restrictions.StatementRestrictions.requiresAllowFilteringIfNotSpecified;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
@ -109,7 +150,7 @@ import static org.apache.cassandra.utils.ByteBufferUtil.UNSET_BYTE_BUFFER;
* Note that select statements can be accessed by multiple threads, so we cannot rely on mutable attributes.
*/
@ThreadSafe
public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement
public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement, CQLStatement.ReturningCQLStatement
{
private static final Logger logger = LoggerFactory.getLogger(SelectStatement.class);
private static final NoSpamLogger noSpamLogger = NoSpamLogger.getLogger(SelectStatement.logger, 1, TimeUnit.MINUTES);
@ -147,7 +188,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement
private final ColumnComparator<List<ByteBuffer>> orderingComparator;
// Used by forSelection below
private static final Parameters defaultParameters = new Parameters(Collections.emptyList(),
public static final Parameters defaultParameters = new Parameters(Collections.emptyList(),
Collections.emptyList(),
false,
false,
@ -243,6 +284,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement
null);
}
@Override
public ResultSet.ResultMetadata getResultMetadata()
{
return selection.getResultMetadata();
@ -911,6 +953,11 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement
return getLimit(limit, options);
}
public boolean isLimitMarker()
{
return limit instanceof Marker;
}
/**
* Returns the per partition limit specified by the user.
* May be used by custom QueryHandler implementations
@ -1186,10 +1233,28 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement
{
// Cache locally for use by Guardrails
this.state = state;
return prepare(state, false);
return prepare(state, false, bindVariables);
}
public SelectStatement prepare(ClientState state, boolean forView) throws InvalidRequestException
public SelectStatement prepare(ClientState state, boolean forView)
{
return prepare(state, forView, bindVariables);
}
public SelectStatement prepare(VariableSpecifications variableSpecifications)
{
return prepare(state, false, variableSpecifications);
}
public SelectStatement prepare(boolean forView)
{
return prepare(state, forView, bindVariables);
}
/**
* @throws InvalidRequestException if the statement being prepared is invalid
*/
public SelectStatement prepare(ClientState state, boolean forView, VariableSpecifications variableSpecifications) throws InvalidRequestException
{
TableMetadata table = Schema.instance.validateTable(keyspace(), name());
@ -1197,7 +1262,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement
boolean containsOnlyStaticColumns = selectOnlyStaticColumns(table, selectables);
List<Ordering> orderings = getOrderings(table);
StatementRestrictions restrictions = prepareRestrictions(state, table, bindVariables, orderings, containsOnlyStaticColumns, forView);
StatementRestrictions restrictions = prepareRestrictions(state, table, variableSpecifications, orderings, containsOnlyStaticColumns, forView);
// If we order post-query, the sorted column needs to be in the ResultSet for sorting,
// even if we don't ultimately ship them to the client (CASSANDRA-4911).
@ -1206,7 +1271,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement
Selection selection = prepareSelection(table,
selectables,
bindVariables,
variableSpecifications,
resultSetOrderingColumns,
restrictions);
@ -1242,15 +1307,15 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement
checkNeedsFiltering(table, restrictions);
return new SelectStatement(table,
bindVariables,
variableSpecifications,
parameters,
selection,
restrictions,
isReversed,
aggregationSpecFactory,
orderingComparator,
prepareLimit(bindVariables, limit, keyspace(), limitReceiver()),
prepareLimit(bindVariables, perPartitionLimit, keyspace(), perPartitionLimitReceiver()));
prepareLimit(variableSpecifications, limit, keyspace(), limitReceiver()),
prepareLimit(variableSpecifications, perPartitionLimit, keyspace(), perPartitionLimitReceiver()));
}
private Set<ColumnMetadata> getResultSetOrdering(StatementRestrictions restrictions, Map<ColumnMetadata, Ordering> orderingColumns)
@ -1620,18 +1685,30 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement
public final boolean isDistinct;
public final boolean allowFiltering;
public final boolean isJson;
public final String refName;
public Parameters(List<Ordering.Raw> orderings,
List<Selectable.Raw> groups,
boolean isDistinct,
boolean allowFiltering,
boolean isJson)
{
this(orderings, groups, isDistinct, allowFiltering, isJson, null);
}
public Parameters(List<Ordering.Raw> orderings,
List<Selectable.Raw> groups,
boolean isDistinct,
boolean allowFiltering,
boolean isJson,
String refName)
{
this.orderings = orderings;
this.groups = groups;
this.isDistinct = isDistinct;
this.allowFiltering = allowFiltering;
this.isJson = isJson;
this.refName = refName;
}
}
@ -1810,7 +1887,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement
}
}
private String asCQL(QueryOptions options, ClientState state)
public String asCQL(QueryOptions options, ClientState state)
{
ColumnFilter columnFilter = selection.newSelectors(options).getColumnFilter();
StringBuilder sb = new StringBuilder();

View File

@ -0,0 +1,563 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.cql3.statements;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import accord.api.Key;
import accord.primitives.Keys;
import accord.primitives.Txn;
import org.apache.cassandra.audit.AuditLogContext;
import org.apache.cassandra.audit.AuditLogEntryType;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.CQLStatement;
import org.apache.cassandra.cql3.ColumnSpecification;
import org.apache.cassandra.cql3.QueryOptions;
import org.apache.cassandra.cql3.ResultSet;
import org.apache.cassandra.cql3.VariableSpecifications;
import org.apache.cassandra.cql3.selection.ResultSetBuilder;
import org.apache.cassandra.cql3.selection.Selection;
import org.apache.cassandra.cql3.transactions.ConditionStatement;
import org.apache.cassandra.cql3.transactions.ReferenceOperation;
import org.apache.cassandra.cql3.transactions.RowDataReference;
import org.apache.cassandra.cql3.transactions.SelectReferenceSource;
import org.apache.cassandra.db.ReadQuery;
import org.apache.cassandra.db.SinglePartitionReadCommand;
import org.apache.cassandra.db.SinglePartitionReadQuery;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.partitions.FilteredPartition;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.QueryState;
import org.apache.cassandra.service.accord.AccordService;
import org.apache.cassandra.service.accord.txn.TxnCondition;
import org.apache.cassandra.service.accord.txn.TxnData;
import org.apache.cassandra.service.accord.txn.TxnDataName;
import org.apache.cassandra.service.accord.txn.TxnNamedRead;
import org.apache.cassandra.service.accord.txn.TxnQuery;
import org.apache.cassandra.service.accord.txn.TxnRead;
import org.apache.cassandra.service.accord.txn.TxnReference;
import org.apache.cassandra.service.accord.txn.TxnUpdate;
import org.apache.cassandra.service.accord.txn.TxnWrite;
import org.apache.cassandra.transport.Dispatcher;
import org.apache.cassandra.transport.messages.ResultMessage;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.LazyToString;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkTrue;
public class TransactionStatement implements CQLStatement.CompositeCQLStatement, CQLStatement.ReturningCQLStatement
{
private static final Logger logger = LoggerFactory.getLogger(TransactionStatement.class);
public static final String DUPLICATE_TUPLE_NAME_MESSAGE = "The name '%s' has already been used by a LET assignment.";
public static final String INCOMPLETE_PRIMARY_KEY_LET_MESSAGE = "SELECT in LET assignment must specify either all primary key elements or all partition key elements and LIMIT 1. In both cases partition key elements must be always specified with equality operators; CQL %s";
public static final String INCOMPLETE_PRIMARY_KEY_SELECT_MESSAGE = "Normal SELECT must specify either all primary key elements or all partition key elements and LIMIT 1. In both cases partition key elements must be always specified with equality operators; CQL %s";
public static final String NO_CONDITIONS_IN_UPDATES_MESSAGE = "Updates within transactions may not specify their own conditions.";
public static final String NO_TIMESTAMPS_IN_UPDATES_MESSAGE = "Updates within transactions may not specify custom timestamps.";
public static final String EMPTY_TRANSACTION_MESSAGE = "Transaction contains no reads or writes";
public static final String SELECT_REFS_NEED_COLUMN_MESSAGE = "SELECT references must specify a column.";
public static final String TRANSACTIONS_DISABLED_MESSAGE = "Accord transactions are disabled. (See accord_transactions_enabled in cassandra.yaml)";
public static final String ILLEGAL_RANGE_QUERY_MESSAGE = "Range queries are not allowed for reads within a transaction";
static class NamedSelect
{
final TxnDataName name;
final SelectStatement select;
public NamedSelect(TxnDataName name, SelectStatement select)
{
this.name = name;
this.select = select;
}
}
private final List<NamedSelect> assignments;
private final NamedSelect returningSelect;
private final List<RowDataReference> returningReferences;
private final List<ModificationStatement> updates;
private final List<ConditionStatement> conditions;
private final VariableSpecifications bindVariables;
private final ResultSet.ResultMetadata resultMetadata;
public TransactionStatement(List<NamedSelect> assignments,
NamedSelect returningSelect,
List<RowDataReference> returningReferences,
List<ModificationStatement> updates,
List<ConditionStatement> conditions,
VariableSpecifications bindVariables)
{
this.assignments = assignments;
this.returningSelect = returningSelect;
this.returningReferences = returningReferences;
this.updates = updates;
this.conditions = conditions;
this.bindVariables = bindVariables;
if (returningSelect != null)
{
resultMetadata = returningSelect.select.getResultMetadata();
}
else if (returningReferences != null && !returningReferences.isEmpty())
{
List<ColumnSpecification> names = new ArrayList<>(returningReferences.size());
for (RowDataReference reference : returningReferences)
names.add(reference.toResultMetadata());
resultMetadata = new ResultSet.ResultMetadata(names);
}
else
{
resultMetadata = ResultSet.ResultMetadata.EMPTY;
}
}
public List<ModificationStatement> getUpdates()
{
return updates;
}
@Override
public ImmutableList<ColumnSpecification> getBindVariables()
{
return bindVariables.getImmutableBindVariables();
}
@Override
public void authorize(ClientState state)
{
// Assess read permissions for all data from both explicit LET statements and generated reads.
for (NamedSelect let : assignments)
let.select.authorize(state);
if (returningSelect != null)
returningSelect.select.authorize(state);
for (ModificationStatement update : updates)
update.authorize(state);
}
@Override
public void validate(ClientState state)
{
for (NamedSelect statement : assignments)
statement.select.validate(state);
if (returningSelect != null)
returningSelect.select.validate(state);
for (ModificationStatement statement : updates)
statement.validate(state);
}
@Override
public Iterable<CQLStatement> getStatements()
{
return () -> {
Stream<CQLStatement> stream = assignments.stream().map(n -> n.select);
if (returningSelect != null)
stream = Stream.concat(stream, Stream.of(returningSelect.select));
stream = Stream.concat(stream, updates.stream());
return stream.iterator();
};
}
@Override
public ResultSet.ResultMetadata getResultMetadata()
{
return resultMetadata;
}
TxnNamedRead createNamedRead(NamedSelect namedSelect, QueryOptions options, ClientState state)
{
SelectStatement select = namedSelect.select;
ReadQuery readQuery = select.getQuery(options, 0);
checkTrue(readQuery instanceof SinglePartitionReadQuery.Group, ILLEGAL_RANGE_QUERY_MESSAGE, select.asCQL(options, state));
// We reject reads from both LET and SELECT that do not specify a single row.
@SuppressWarnings("unchecked")
SinglePartitionReadQuery.Group<SinglePartitionReadCommand> selectQuery = (SinglePartitionReadQuery.Group<SinglePartitionReadCommand>) readQuery;
if (selectQuery.queries.size() != 1)
throw new IllegalArgumentException("Within a transaction, SELECT statements must select a single partition; found " + selectQuery.queries.size() + " partitions");
return new TxnNamedRead(namedSelect.name, Iterables.getOnlyElement(selectQuery.queries));
}
List<TxnNamedRead> createNamedReads(NamedSelect namedSelect, QueryOptions options, ClientState state)
{
SelectStatement select = namedSelect.select;
ReadQuery readQuery = select.getQuery(options, 0);
checkTrue(readQuery instanceof SinglePartitionReadQuery.Group, ILLEGAL_RANGE_QUERY_MESSAGE, select.asCQL(options, state));
// We reject reads from both LET and SELECT that do not specify a single row.
@SuppressWarnings("unchecked")
SinglePartitionReadQuery.Group<SinglePartitionReadCommand> selectQuery = (SinglePartitionReadQuery.Group<SinglePartitionReadCommand>) readQuery;
if (selectQuery.queries.size() == 1)
return Collections.singletonList(new TxnNamedRead(namedSelect.name, Iterables.getOnlyElement(selectQuery.queries)));
List<TxnNamedRead> list = new ArrayList<>(selectQuery.queries.size());
for (int i = 0; i < selectQuery.queries.size(); i++)
list.add(new TxnNamedRead(TxnDataName.returning(i), selectQuery.queries.get(i)));
return list;
}
private List<TxnNamedRead> createNamedReads(QueryOptions options, ClientState state, Map<TxnDataName, NamedSelect> autoReads, Consumer<Key> keyConsumer)
{
List<TxnNamedRead> reads = new ArrayList<>(assignments.size() + 1);
for (NamedSelect select : assignments)
{
TxnNamedRead read = createNamedRead(select, options, state);
keyConsumer.accept(read.key());
reads.add(read);
}
if (returningSelect != null)
{
for (TxnNamedRead read : createNamedReads(returningSelect, options, state))
{
keyConsumer.accept(read.key());
reads.add(read);
}
}
for (NamedSelect select : autoReads.values())
// don't need keyConsumer as the keys are known to exist due to Modification
reads.add(createNamedRead(select, options, state));
return reads;
}
TxnCondition createCondition(QueryOptions options)
{
if (conditions.isEmpty())
return TxnCondition.none();
if (conditions.size() == 1)
return conditions.get(0).createCondition(options);
List<TxnCondition> result = new ArrayList<>(conditions.size());
for (ConditionStatement condition : conditions)
result.add(condition.createCondition(options));
// TODO: OR support
return new TxnCondition.BooleanGroup(TxnCondition.Kind.AND, result);
}
List<TxnWrite.Fragment> createWriteFragments(ClientState state, QueryOptions options, Map<TxnDataName, NamedSelect> autoReads, Consumer<Key> keyConsumer)
{
List<TxnWrite.Fragment> fragments = new ArrayList<>(updates.size());
int idx = 0;
for (ModificationStatement modification : updates)
{
TxnWrite.Fragment fragment = modification.getTxnWriteFragment(idx, state, options);
keyConsumer.accept(fragment.key);
fragments.add(fragment);
if (modification.allReferenceOperations().stream().anyMatch(ReferenceOperation::requiresRead))
{
// Reads are not merged by partition here due to potentially differing columns retrieved, etc.
TxnDataName partitionName = TxnDataName.partitionRead(modification.metadata(), fragment.key.partitionKey(), idx);
if (!autoReads.containsKey(partitionName))
autoReads.put(partitionName, new NamedSelect(partitionName, modification.createSelectForTxn()));
}
idx++;
}
return fragments;
}
TxnUpdate createUpdate(ClientState state, QueryOptions options, Map<TxnDataName, NamedSelect> autoReads, Consumer<Key> keyConsumer)
{
return new TxnUpdate(createWriteFragments(state, options, autoReads, keyConsumer), createCondition(options));
}
Keys toKeys(SortedSet<Key> keySet)
{
return new Keys(keySet);
}
@VisibleForTesting
public Txn createTxn(ClientState state, QueryOptions options)
{
SortedSet<Key> keySet = new TreeSet<>();
if (updates.isEmpty())
{
// TODO: Test case around this...
Preconditions.checkState(conditions.isEmpty(), "No condition should exist without updates present");
List<TxnNamedRead> reads = createNamedReads(options, state, ImmutableMap.of(), keySet::add);
Keys txnKeys = toKeys(keySet);
TxnRead read = new TxnRead(reads, txnKeys);
return new Txn.InMemory(txnKeys, read, TxnQuery.ALL);
}
else
{
Map<TxnDataName, NamedSelect> autoReads = new HashMap<>();
TxnUpdate update = createUpdate(state, options, autoReads, keySet::add);
List<TxnNamedRead> reads = createNamedReads(options, state, autoReads, keySet::add);
Keys txnKeys = toKeys(keySet);
TxnRead read = new TxnRead(reads, txnKeys);
return new Txn.InMemory(txnKeys, read, TxnQuery.ALL, update);
}
}
private static void checkAtMostOneRowSpecified(ClientState clientState, @Nullable QueryOptions options, SelectStatement select, String failureMessage)
{
if (select.getRestrictions().hasAllPrimaryKeyColumnsRestrictedByEqualities())
return;
if (options == null)
{
// If the limit is a non-terminal marker (because we're preparing), defer validation until execution.
if (select.isLimitMarker())
return;
// The limit is already defined, so proceed with validation...
options = QueryOptions.DEFAULT;
}
int limit = select.getLimit(options);
QueryOptions finalOptions = options; // javac thinks this is mutable so requires a copy
checkTrue(limit == 1 && select.getRestrictions().hasAllPartitionKeyColumnsRestrictedByEqualities(), failureMessage, LazyToString.lazy(() -> select.asCQL(finalOptions, clientState)));
}
@Override
public ResultMessage execute(QueryState state, QueryOptions options, Dispatcher.RequestTime requestTime)
{
checkTrue(DatabaseDescriptor.getAccordTransactionsEnabled(), TRANSACTIONS_DISABLED_MESSAGE);
try
{
for (NamedSelect assignment : assignments)
checkAtMostOneRowSpecified(state.getClientState(), options, assignment.select, INCOMPLETE_PRIMARY_KEY_LET_MESSAGE);
if (returningSelect != null)
checkAtMostOneRowSpecified(state.getClientState(), options, returningSelect.select, INCOMPLETE_PRIMARY_KEY_SELECT_MESSAGE);
TxnData data = AccordService.instance().coordinate(createTxn(state.getClientState(), options), options.getConsistency());
if (returningSelect != null)
{
ReadQuery readQuery = returningSelect.select.getQuery(options, 0);
checkTrue(readQuery instanceof SinglePartitionReadQuery.Group, ILLEGAL_RANGE_QUERY_MESSAGE, returningSelect.select.asCQL(options, state.getClientState()));
@SuppressWarnings("unchecked")
SinglePartitionReadQuery.Group<SinglePartitionReadCommand> selectQuery = (SinglePartitionReadQuery.Group<SinglePartitionReadCommand>) readQuery;
Selection.Selectors selectors = returningSelect.select.getSelection().newSelectors(options);
ResultSetBuilder result = new ResultSetBuilder(resultMetadata, selectors, false);
if (selectQuery.queries.size() == 1)
{
FilteredPartition partition = data.get(TxnDataName.returning());
if (partition != null)
returningSelect.select.processPartition(partition.rowIterator(), options, result, FBUtilities.nowInSeconds());
}
else
{
long nowInSec = FBUtilities.nowInSeconds();
for (int i = 0; i < selectQuery.queries.size(); i++)
{
FilteredPartition partition = data.get(TxnDataName.returning(i));
if (partition != null)
returningSelect.select.processPartition(partition.rowIterator(), options, result, nowInSec);
}
}
return new ResultMessage.Rows(result.build());
}
if (returningReferences != null)
{
List<AbstractType<?>> resultType = new ArrayList<>(returningReferences.size());
List<ColumnMetadata> columns = new ArrayList<>(returningReferences.size());
for (RowDataReference reference : returningReferences)
{
ColumnMetadata forMetadata = reference.toResultMetadata();
resultType.add(forMetadata.type);
columns.add(reference.column());
}
ResultSetBuilder result = new ResultSetBuilder(resultMetadata, Selection.noopSelector(), false);
result.newRow(options.getProtocolVersion(), null, null, columns);
for (int i = 0; i < returningReferences.size(); i++)
{
RowDataReference reference = returningReferences.get(i);
TxnReference txnReference = reference.toTxnReference(options);
ByteBuffer buffer = txnReference.toByteBuffer(data, resultType.get(i));
result.add(buffer);
}
return new ResultMessage.Rows(result.build());
}
// In the case of a write-only transaction, just return and empty result.
// TODO: This could be modified to return an indication of whether a condition (if present) succeeds.
return new ResultMessage.Void();
}
catch (Throwable t)
{
logger.error("Unexpected error with transaction", t);
throw t;
}
}
@Override
public ResultMessage executeLocally(QueryState state, QueryOptions options)
{
return execute(state, options, Dispatcher.RequestTime.forImmediateExecution());
}
@Override
public AuditLogContext getAuditLogContext()
{
return new AuditLogContext(AuditLogEntryType.TRANSACTION);
}
@Override
public boolean eligibleAsPreparedStatement()
{
// false is the default, but still best to be explicit.
return false;
}
public static class Parsed extends QualifiedStatement.Composite
{
private final List<SelectStatement.RawStatement> assignments;
private final SelectStatement.RawStatement select;
private final List<RowDataReference.Raw> returning;
private final List<ModificationStatement.Parsed> updates;
private final List<ConditionStatement.Raw> conditions;
private final List<RowDataReference.Raw> dataReferences;
public Parsed(List<SelectStatement.RawStatement> assignments,
SelectStatement.RawStatement select,
List<RowDataReference.Raw> returning,
List<ModificationStatement.Parsed> updates,
List<ConditionStatement.Raw> conditions,
List<RowDataReference.Raw> dataReferences)
{
this.assignments = assignments;
this.select = select;
this.returning = returning;
this.updates = updates;
this.conditions = conditions != null ? conditions : Collections.emptyList();
this.dataReferences = dataReferences;
}
@Override
protected Iterable<? extends QualifiedStatement> getStatements()
{
Iterable<QualifiedStatement> group = Iterables.concat(assignments, updates);
if (select != null)
group = Iterables.concat(group, Collections.singleton(select));
return group;
}
@Override
public CQLStatement prepare(ClientState state)
{
checkFalse(updates.isEmpty() && returning == null && select == null, EMPTY_TRANSACTION_MESSAGE);
if (select != null || returning != null)
checkTrue(select != null ^ returning != null, "Cannot specify both a full SELECT and a SELECT w/ LET references.");
List<NamedSelect> preparedAssignments = new ArrayList<>(assignments.size());
Map<TxnDataName, RowDataReference.ReferenceSource> refSources = new HashMap<>();
Set<TxnDataName> selectNames = new HashSet<>();
for (SelectStatement.RawStatement select : assignments)
{
checkNotNull(select.parameters.refName, "Assignments must be named");
TxnDataName name = TxnDataName.user(select.parameters.refName);
checkTrue(selectNames.add(name), DUPLICATE_TUPLE_NAME_MESSAGE, name.name());
SelectStatement prepared = select.prepare(bindVariables);
NamedSelect namedSelect = new NamedSelect(name, prepared);
checkAtMostOneRowSpecified(state, null, namedSelect.select, INCOMPLETE_PRIMARY_KEY_LET_MESSAGE);
preparedAssignments.add(namedSelect);
refSources.put(name, new SelectReferenceSource(prepared));
}
if (dataReferences != null)
for (RowDataReference.Raw reference : dataReferences)
reference.resolveReference(refSources);
NamedSelect returningSelect = null;
if (select != null)
{
returningSelect = new NamedSelect(TxnDataName.returning(), select.prepare(bindVariables));
checkAtMostOneRowSpecified(state, null, returningSelect.select, INCOMPLETE_PRIMARY_KEY_SELECT_MESSAGE);
}
List<RowDataReference> returningReferences = null;
if (returning != null)
{
// TODO: Eliminate/modify this check if we allow full tuple selections.
returningReferences = returning.stream().peek(raw -> checkTrue(raw.column() != null, SELECT_REFS_NEED_COLUMN_MESSAGE))
.map(RowDataReference.Raw::prepareAsReceiver)
.collect(Collectors.toList());
}
List<ModificationStatement> preparedUpdates = new ArrayList<>(updates.size());
// check for any read-before-write updates
for (int i = 0; i < updates.size(); i++)
{
ModificationStatement.Parsed parsed = updates.get(i);
ModificationStatement prepared = parsed.prepare(state, bindVariables);
checkFalse(prepared.hasConditions(), NO_CONDITIONS_IN_UPDATES_MESSAGE);
checkFalse(prepared.isTimestampSet(), NO_TIMESTAMPS_IN_UPDATES_MESSAGE);
preparedUpdates.add(prepared);
}
List<ConditionStatement> preparedConditions = new ArrayList<>(conditions.size());
for (ConditionStatement.Raw condition : conditions)
// TODO: If we eventually support IF ks.function(ref) THEN, the keyspace will have to be provided here
preparedConditions.add(condition.prepare("[txn]", bindVariables));
return new TransactionStatement(preparedAssignments, returningSelect, returningReferences, preparedUpdates, preparedConditions, bindVariables);
}
}
}

View File

@ -18,10 +18,13 @@
package org.apache.cassandra.cql3.statements;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import com.google.common.base.Preconditions;
import org.apache.cassandra.audit.AuditLogContext;
import org.apache.cassandra.audit.AuditLogEntryType;
import org.apache.cassandra.cql3.*;
@ -31,6 +34,8 @@ import org.apache.cassandra.cql3.constraints.ColumnConstraint;
import org.apache.cassandra.cql3.restrictions.StatementRestrictions;
import org.apache.cassandra.cql3.terms.Constants;
import org.apache.cassandra.cql3.terms.Term;
import org.apache.cassandra.cql3.transactions.ReferenceOperation;
import org.apache.cassandra.cql3.transactions.ReferenceValue;
import org.apache.cassandra.db.Clustering;
import org.apache.cassandra.db.Slice;
import org.apache.cassandra.db.partitions.PartitionUpdate;
@ -39,6 +44,7 @@ import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.accord.txn.TxnReferenceOperation;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.Pair;
import org.apache.commons.lang3.builder.ToStringBuilder;
@ -53,6 +59,9 @@ import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse
*/
public class UpdateStatement extends ModificationStatement
{
public static final String UPDATING_PRIMARY_KEY_MESSAGE = "PRIMARY KEY part %s found in SET part";
public static final String CANNOT_SET_KEY_WITH_REFERENCE_MESSAGE = "Value reference %s cannot be used to insert PRIMARY KEY column %s";
private static final Constants.Value EMPTY = new Constants.Value(ByteBufferUtil.EMPTY_BYTE_BUFFER);
private UpdateStatement(StatementType type,
@ -174,8 +183,16 @@ public class UpdateStatement extends ModificationStatement
if (def.isPrimaryKeyColumn())
{
checkFalse(value instanceof ReferenceValue.Raw, String.format(CANNOT_SET_KEY_WITH_REFERENCE_MESSAGE, value, def));
whereClause.add(Relation.singleColumn(columnNames.get(i), Operator.EQ, value));
}
else if (value instanceof ReferenceValue.Raw)
{
ReferenceValue.Raw raw = (ReferenceValue.Raw) value;
ReferenceValue referenceValue = raw.prepare(def, bindVariables);
ReferenceOperation operation = new ReferenceOperation(def, TxnReferenceOperation.Kind.setterFor(def), null, null, referenceValue);
operations.add(def, operation);
}
else
{
Operation operation = new Operation.SetValue(value).prepare(metadata, def, !conditions.isEmpty());
@ -277,11 +294,59 @@ public class UpdateStatement extends ModificationStatement
}
}
public static class OperationCollector
{
public final List<Pair<ColumnIdentifier, Operation.RawUpdate>> operations = new ArrayList<>();
public final List<Pair<ColumnIdentifier, ReferenceOperation.Raw>> referenceOps = new ArrayList<>();
public boolean conflictsWithExistingUpdate(ColumnIdentifier column, Operation.RawUpdate update)
{
for (Pair<ColumnIdentifier, Operation.RawUpdate> p : operations)
{
if (p.left.equals(column) && !p.right.isCompatibleWith(update))
return true;
}
return false;
}
public boolean conflictsWithExistingSubstitution(ColumnIdentifier column)
{
for (Pair<ColumnIdentifier, ReferenceOperation.Raw> p : referenceOps)
{
if (p.left.equals(column))
return true;
}
return false;
}
public void addRawUpdate(ColumnIdentifier column, Operation.RawUpdate update)
{
operations.add(Pair.create(column, update));
}
public boolean conflictsWithExistingUpdate(ColumnIdentifier column)
{
for (Pair<ColumnIdentifier, Operation.RawUpdate> p : operations)
{
if (p.left.equals(column))
return true;
}
return false;
}
public void addRawReferenceOperation(ColumnIdentifier column, ReferenceOperation.Raw substitution)
{
// TODO: Make sure there's more than a tuple name here...i.e. an actual reference column?
referenceOps.add(Pair.create(column, substitution));
}
}
public static class ParsedUpdate extends ModificationStatement.Parsed
{
// Provided for an UPDATE
private final List<Pair<ColumnIdentifier, Operation.RawUpdate>> updates;
private final OperationCollector updates;
private final WhereClause whereClause;
private final boolean isForTxn;
/**
* Creates a new UpdateStatement from a column family name, columns map, consistency
@ -295,14 +360,16 @@ public class UpdateStatement extends ModificationStatement
* */
public ParsedUpdate(QualifiedName name,
Attributes.Raw attrs,
List<Pair<ColumnIdentifier, Operation.RawUpdate>> updates,
OperationCollector updates,
WhereClause whereClause,
List<ColumnCondition.Raw> conditions,
boolean ifExists)
boolean ifExists,
boolean isForTxn)
{
super(name, StatementType.UPDATE, attrs, conditions, false, ifExists);
this.updates = updates;
this.whereClause = whereClause;
this.isForTxn = isForTxn;
}
@Override
@ -314,17 +381,24 @@ public class UpdateStatement extends ModificationStatement
{
Operations operations = new Operations(type);
for (Pair<ColumnIdentifier, Operation.RawUpdate> entry : updates)
for (Pair<ColumnIdentifier, Operation.RawUpdate> entry : updates.operations)
{
ColumnMetadata def = metadata.getExistingColumn(entry.left);
checkFalse(def.isPrimaryKeyColumn(), "PRIMARY KEY part %s found in SET part", def.name);
Operation operation = entry.right.prepare(metadata, def, !conditions.isEmpty());
checkFalse(def.isPrimaryKeyColumn(), UPDATING_PRIMARY_KEY_MESSAGE, def.name);
Operation operation = entry.right.prepare(metadata, def, !conditions.isEmpty() || isForTxn);
operation.collectMarkerSpecification(bindVariables);
operations.add(operation);
}
Preconditions.checkState(updates.referenceOps.isEmpty() || isForTxn);
for (Pair<ColumnIdentifier, ReferenceOperation.Raw> entry : updates.referenceOps)
{
ColumnMetadata def = metadata.getExistingColumn(entry.left);
checkFalse(def.isPrimaryKeyColumn(), UPDATING_PRIMARY_KEY_MESSAGE, def.name);
ReferenceOperation operation = entry.right.prepare(metadata, bindVariables);
operations.add(def, operation);
}
StatementRestrictions restrictions = newRestrictions(state,
metadata,
bindVariables,

View File

@ -20,7 +20,6 @@ package org.apache.cassandra.cql3.terms;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import org.apache.cassandra.cql3.AssignmentTestable;
import org.apache.cassandra.cql3.CQL3Type;
@ -30,7 +29,24 @@ import org.apache.cassandra.cql3.QueryOptions;
import org.apache.cassandra.cql3.UpdateParameters;
import org.apache.cassandra.db.Clustering;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.marshal.*;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.AsciiType;
import org.apache.cassandra.db.marshal.BooleanType;
import org.apache.cassandra.db.marshal.ByteType;
import org.apache.cassandra.db.marshal.BytesType;
import org.apache.cassandra.db.marshal.CounterColumnType;
import org.apache.cassandra.db.marshal.DecimalType;
import org.apache.cassandra.db.marshal.DoubleType;
import org.apache.cassandra.db.marshal.DurationType;
import org.apache.cassandra.db.marshal.Int32Type;
import org.apache.cassandra.db.marshal.IntegerType;
import org.apache.cassandra.db.marshal.LongType;
import org.apache.cassandra.db.marshal.NumberType;
import org.apache.cassandra.db.marshal.ReversedType;
import org.apache.cassandra.db.marshal.StringType;
import org.apache.cassandra.db.marshal.TimeUUIDType;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.db.marshal.UUIDType;
import org.apache.cassandra.db.rows.Cell;
import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.exceptions.InvalidRequestException;
@ -39,12 +55,13 @@ import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FastByteOperations;
import static java.nio.charset.StandardCharsets.US_ASCII;
/**
* Static helper methods and classes for constants.
*/
public abstract class Constants
{
private static ByteBuffer getCurrentCellBuffer(ColumnMetadata column, DecoratedKey key, UpdateParameters params)
{
Row currentRow = params.getPrefetchedRow(key, column.isStatic() ? Clustering.STATIC_CLUSTERING : params.currentClustering());
@ -59,7 +76,7 @@ public abstract class Constants
@Override
public AbstractType<?> getPreferedTypeFor(String text)
{
if (StandardCharsets.US_ASCII.newEncoder().canEncode(text))
if (US_ASCII.newEncoder().canEncode(text))
{
return AsciiType.instance;
}
@ -272,6 +289,7 @@ public abstract class Constants
return new Literal(Type.DURATION, text);
}
@Override
public Value prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException
{
if (!testAssignment(keyspace, receiver).isAssignable())

View File

@ -312,7 +312,7 @@ public abstract class Lists
public static class SetterByIndex extends Operation
{
private final Term idx;
public final Term idx;
public SetterByIndex(ColumnMetadata column, Term idx, Term t)
{

View File

@ -267,7 +267,7 @@ public final class Maps
public static class SetterByKey extends Operation
{
private final Term k;
public final Term k;
public SetterByKey(ColumnMetadata column, Term k, Term t)
{

View File

@ -255,7 +255,7 @@ public final class UserTypes
public static class SetterByField extends Operation
{
private final FieldIdentifier field;
public final FieldIdentifier field;
public SetterByField(ColumnMetadata column, FieldIdentifier field, Term t)
{

View File

@ -0,0 +1,148 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.cql3.transactions;
import com.google.common.base.Preconditions;
import org.apache.cassandra.cql3.ColumnSpecification;
import org.apache.cassandra.cql3.QueryOptions;
import org.apache.cassandra.cql3.terms.Term;
import org.apache.cassandra.cql3.VariableSpecifications;
import org.apache.cassandra.service.accord.txn.TxnCondition;
public class ConditionStatement
{
public enum Kind
{
IS_NOT_NULL(TxnCondition.Kind.IS_NOT_NULL, null),
IS_NULL(TxnCondition.Kind.IS_NULL, null),
EQ(TxnCondition.Kind.EQUAL, TxnCondition.Kind.EQUAL),
NEQ(TxnCondition.Kind.NOT_EQUAL, TxnCondition.Kind.NOT_EQUAL),
GT(TxnCondition.Kind.GREATER_THAN, TxnCondition.Kind.LESS_THAN),
GTE(TxnCondition.Kind.GREATER_THAN_OR_EQUAL, TxnCondition.Kind.LESS_THAN_OR_EQUAL),
LT(TxnCondition.Kind.LESS_THAN, TxnCondition.Kind.GREATER_THAN),
LTE(TxnCondition.Kind.LESS_THAN_OR_EQUAL, TxnCondition.Kind.GREATER_THAN_OR_EQUAL);
// TODO: Support for IN, CONTAINS, CONTAINS KEY
private final TxnCondition.Kind kind;
private final TxnCondition.Kind reversedKind;
Kind(TxnCondition.Kind kind, TxnCondition.Kind reversedKind)
{
this.kind = kind;
this.reversedKind = reversedKind;
}
TxnCondition.Kind toTxnKind(boolean reversed)
{
return reversed ? reversedKind : kind;
}
}
private final RowDataReference reference;
private final Kind kind;
private final Term value;
private final boolean reversed;
public ConditionStatement(RowDataReference reference, Kind kind, Term value, boolean reversed)
{
this.reference = reference;
this.kind = kind;
this.value = value;
this.reversed = reversed;
}
public static class Raw
{
private final Term.Raw lhs;
private final Kind kind;
private final Term.Raw rhs;
public Raw(Term.Raw lhs, Kind kind, Term.Raw rhs)
{
Preconditions.checkArgument(lhs != null);
Preconditions.checkArgument((rhs == null) == (kind == Kind.IS_NOT_NULL || kind == Kind.IS_NULL));
this.lhs = lhs;
this.kind = kind;
this.rhs = rhs;
}
public ConditionStatement prepare(String keyspace, VariableSpecifications bindVariables)
{
if (rhs == null)
{
// In the IS NULL/IS NOT NULL case, the reference will always be on the LHS
RowDataReference reference = ((RowDataReference.Raw) lhs).prepareAsReceiver();
reference.collectMarkerSpecification(bindVariables);
return new ConditionStatement(reference, kind, null, false);
}
RowDataReference reference;
Term value;
boolean reversed = false;
if (lhs instanceof RowDataReference.Raw)
{
reference = ((RowDataReference.Raw) lhs).prepareAsReceiver();
ColumnSpecification receiver = reference.getValueReceiver();
value = rhs.prepare(keyspace, receiver);
}
else if (rhs instanceof RowDataReference.Raw)
{
reference = ((RowDataReference.Raw) rhs).prepareAsReceiver();
ColumnSpecification receiver = reference.getValueReceiver();
value = lhs.prepare(keyspace, receiver);
// TxnCondition expects the reference to be on the LHS, so reverse the operator.
reversed = true;
}
else
{
throw new IllegalStateException("Either the left-hand or right-hand side must be a reference!");
}
reference.collectMarkerSpecification(bindVariables);
value.collectMarkerSpecification(bindVariables);
return new ConditionStatement(reference, kind, value, reversed);
}
}
public TxnCondition createCondition(QueryOptions options)
{
switch (kind)
{
case IS_NOT_NULL:
case IS_NULL:
return new TxnCondition.Exists(reference.toTxnReference(options), kind.toTxnKind(reversed));
case EQ:
case NEQ:
case GT:
case GTE:
case LT:
case LTE:
// TODO: Support for references on LHS and RHS
return new TxnCondition.Value(reference.toTxnReference(options),
kind.toTxnKind(reversed),
value.bindAndGet(options),
options.getProtocolVersion());
default:
throw new IllegalStateException();
}
}
}

View File

@ -0,0 +1,178 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.cql3.transactions;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.FieldIdentifier;
import org.apache.cassandra.cql3.Operation;
import org.apache.cassandra.cql3.QueryOptions;
import org.apache.cassandra.cql3.VariableSpecifications;
import org.apache.cassandra.cql3.terms.Lists;
import org.apache.cassandra.cql3.terms.Maps;
import org.apache.cassandra.cql3.terms.Term;
import org.apache.cassandra.cql3.terms.UserTypes;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.CollectionType;
import org.apache.cassandra.db.marshal.MapType;
import org.apache.cassandra.db.marshal.SetType;
import org.apache.cassandra.db.marshal.UserType;
import org.apache.cassandra.db.rows.CellPath;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.accord.txn.TxnReferenceOperation;
import org.apache.cassandra.utils.ByteBufferUtil;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkTrue;
import static org.apache.cassandra.db.marshal.CollectionType.Kind.MAP;
import static org.apache.cassandra.schema.TableMetadata.UNDEFINED_COLUMN_NAME_MESSAGE;
public class ReferenceOperation
{
private final ColumnMetadata receiver;
private final TxnReferenceOperation.Kind kind;
private final FieldIdentifier field;
private final Term key;
private final ReferenceValue value;
public ReferenceOperation(ColumnMetadata receiver, TxnReferenceOperation.Kind kind, Term key, FieldIdentifier field, ReferenceValue value)
{
this.receiver = receiver;
this.kind = kind;
this.key = key;
this.field = field;
this.value = value;
}
/**
* Creates a {@link ReferenceOperation} from the given {@link Operation} for the purpose of defering execution
* within a transaction. When the language sees an Operation using a reference one is created already, but for cases
* that needs to defer execution (such as when {@link Operation#requiresRead()} is true), this method can be used.
*/
public static ReferenceOperation create(Operation operation)
{
TxnReferenceOperation.Kind kind = TxnReferenceOperation.Kind.from(operation);
ColumnMetadata receiver = operation.column;
// We already have a prepared reference value, so there is no need to inspect the value type.
ReferenceValue value = new ReferenceValue.Constant(operation.term());
Term key = extractKeyOrIndex(operation);
FieldIdentifier field = extractField(operation);
return new ReferenceOperation(receiver, kind, key, field, value);
}
public TxnReferenceOperation.Kind getKind()
{
return kind;
}
public ReferenceValue getValue()
{
return value;
}
public ColumnMetadata getReceiver()
{
return receiver;
}
public boolean requiresRead()
{
// TODO: Find a better way than delegating to the operation?
return kind.toOperation(receiver, null, null, null).requiresRead();
}
public TxnReferenceOperation bindAndGet(QueryOptions options)
{
return new TxnReferenceOperation(kind,
receiver,
key != null ? key.bindAndGet(options) : null,
field != null ? field.bytes : null,
value.bindAndGet(options));
}
public static class Raw
{
private final Operation.RawUpdate rawUpdate;
public final ColumnIdentifier column;
private final ReferenceValue.Raw value;
public Raw(Operation.RawUpdate rawUpdate, ColumnIdentifier column, ReferenceValue.Raw value)
{
this.rawUpdate = rawUpdate;
this.column = column;
this.value = value;
}
public ReferenceOperation prepare(TableMetadata metadata, VariableSpecifications bindVariables)
{
ColumnMetadata receiver = metadata.getColumn(column);
Operation operation = rawUpdate.prepare(metadata, receiver, true);
TxnReferenceOperation.Kind kind = TxnReferenceOperation.Kind.from(operation);
Term key = extractKeyOrIndex(operation);
checkTrue(receiver != null, UNDEFINED_COLUMN_NAME_MESSAGE, column.toCQLString(), metadata);
AbstractType<?> type = receiver.type;
ColumnMetadata valueReceiver = receiver;
if (type.isCollection())
{
CollectionType<?> collectionType = (CollectionType<?>) type;
// The value for a map subtraction is actually a set (see Operation.Substraction)
if (kind == TxnReferenceOperation.Kind.SetDiscarder && collectionType.kind == MAP)
valueReceiver = valueReceiver.withNewType(SetType.getInstance(((MapType<?, ?>) type).getKeysType(), true));
if (kind == TxnReferenceOperation.Kind.ListSetterByIndex || kind == TxnReferenceOperation.Kind.MapSetterByKey)
valueReceiver = valueReceiver.withNewType(collectionType.valueComparator());
}
FieldIdentifier field = extractField(operation);
if (type.isUDT())
{
if (kind == TxnReferenceOperation.Kind.UserTypeSetterByField)
{
@SuppressWarnings("ConstantConditions") UserType userType = (UserType) type;
CellPath fieldPath = userType.cellPathForField(field);
int i = ByteBufferUtil.getUnsignedShort(fieldPath.get(0), 0);
valueReceiver = valueReceiver.withNewType(userType.fieldType(i));
}
}
return new ReferenceOperation(receiver, kind, key, field, value.prepare(valueReceiver, bindVariables));
}
}
private static FieldIdentifier extractField(Operation operation)
{
if (operation instanceof UserTypes.SetterByField)
return ((UserTypes.SetterByField) operation).field;
return null;
}
private static Term extractKeyOrIndex(Operation operation)
{
// TODO: Is there a way to do this without exposing k and idx?
if (operation instanceof Maps.SetterByKey)
return ((Maps.SetterByKey) operation).k;
else if (operation instanceof Lists.SetterByIndex)
return ((Lists.SetterByIndex) operation).idx;
return null;
}
}

View File

@ -0,0 +1,155 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.cql3.transactions;
import org.apache.cassandra.cql3.ColumnSpecification;
import org.apache.cassandra.cql3.QueryOptions;
import org.apache.cassandra.cql3.terms.Term;
import org.apache.cassandra.cql3.VariableSpecifications;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.service.accord.txn.TxnReferenceValue;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkTrue;
public abstract class ReferenceValue
{
public abstract TxnReferenceValue bindAndGet(QueryOptions options);
public static abstract class Raw extends Term.Raw
{
public abstract ReferenceValue prepare(ColumnMetadata receiver, VariableSpecifications bindVariables);
}
public static class Constant extends ReferenceValue
{
private final Term term;
public Constant(Term term)
{
this.term = term;
}
@Override
public TxnReferenceValue bindAndGet(QueryOptions options)
{
return new TxnReferenceValue.Constant(term.bindAndGet(options));
}
public static class Raw extends ReferenceValue.Raw
{
private final Term.Raw term;
public Raw(Term.Raw term)
{
this.term = term;
}
@Override
public ReferenceValue prepare(ColumnMetadata receiver, VariableSpecifications bindVariables)
{
return new Constant(term.prepare(receiver.ksName, receiver));
}
@Override
public TestResult testAssignment(String keyspace, ColumnSpecification receiver)
{
return term.testAssignment(keyspace, receiver);
}
@Override
public Term prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException
{
return term.prepare(keyspace, receiver);
}
@Override
public String getText()
{
return term.getText();
}
@Override
public AbstractType<?> getExactTypeIfKnown(String keyspace)
{
return term.getExactTypeIfKnown(keyspace);
}
}
}
public static class Substitution extends ReferenceValue
{
private final RowDataReference reference;
public Substitution(RowDataReference reference)
{
this.reference = reference;
}
@Override
public TxnReferenceValue bindAndGet(QueryOptions options)
{
return new TxnReferenceValue.Substitution(reference.toTxnReference(options));
}
public static class Raw extends ReferenceValue.Raw
{
private final RowDataReference.Raw reference;
public Raw(RowDataReference.Raw reference)
{
this.reference = reference;
}
@Override
public ReferenceValue prepare(ColumnMetadata receiver, VariableSpecifications bindVariables)
{
reference.checkResolved();
checkTrue(reference.column() != null, "substitution references must reference a column (%s)", reference);
return new Substitution((RowDataReference) reference.prepare(null, receiver));
}
@Override
public TestResult testAssignment(String keyspace, ColumnSpecification receiver)
{
return reference.testAssignment(keyspace, receiver);
}
@Override
public Term prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException
{
return reference.prepare(keyspace, receiver);
}
@Override
public String getText()
{
return reference.getText();
}
@Override
public AbstractType<?> getExactTypeIfKnown(String keyspace)
{
return reference.getExactTypeIfKnown(keyspace);
}
}
}
}

View File

@ -0,0 +1,405 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.cql3.transactions;
import java.util.List;
import java.util.Map;
import com.google.common.base.Preconditions;
import org.apache.cassandra.cql3.AssignmentTestable;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.ColumnSpecification;
import org.apache.cassandra.cql3.FieldIdentifier;
import org.apache.cassandra.cql3.terms.Lists;
import org.apache.cassandra.cql3.terms.Maps;
import org.apache.cassandra.cql3.QueryOptions;
import org.apache.cassandra.cql3.terms.Sets;
import org.apache.cassandra.cql3.terms.Term;
import org.apache.cassandra.cql3.terms.UserTypes;
import org.apache.cassandra.cql3.VariableSpecifications;
import org.apache.cassandra.cql3.functions.Function;
import org.apache.cassandra.cql3.functions.types.utils.Bytes;
import org.apache.cassandra.cql3.selection.Selectable;
import org.apache.cassandra.db.marshal.ListType;
import org.apache.cassandra.service.accord.txn.TxnDataName;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.CollectionType;
import org.apache.cassandra.db.marshal.MapType;
import org.apache.cassandra.db.marshal.SetType;
import org.apache.cassandra.db.marshal.UserType;
import org.apache.cassandra.db.rows.CellPath;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.service.accord.txn.TxnReference;
import org.apache.cassandra.utils.ByteBufferUtil;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull;
public class RowDataReference extends Term.NonTerminal
{
public static final String CANNOT_FIND_TUPLE_MESSAGE = "Cannot resolve reference to tuple '%s'.";
public static final String COLUMN_NOT_IN_TUPLE_MESSAGE = "Column '%s' does not exist in tuple '%s'.";
private final TxnDataName selectName;
private final ColumnMetadata column;
private final Term elementPath;
private final CellPath fieldPath;
public RowDataReference(TxnDataName selectName, ColumnMetadata column, Term elementPath, CellPath fieldPath)
{
Preconditions.checkArgument(elementPath == null || fieldPath == null, "Cannot specify both element and field paths");
this.selectName = selectName;
this.column = column;
this.elementPath = elementPath;
this.fieldPath = fieldPath;
}
@Override
public void collectMarkerSpecification(VariableSpecifications boundNames)
{
if (elementPath != null)
elementPath.collectMarkerSpecification(boundNames);
}
@Override
public Terminal bind(QueryOptions options) throws InvalidRequestException
{
throw new UnsupportedOperationException();
}
@Override
public boolean containsBindMarker()
{
return elementPath != null && elementPath.containsBindMarker();
}
@Override
public void addFunctionsTo(List<Function> functions)
{
throw new UnsupportedOperationException("Functions are not currently supported w/ reference terms.");
}
public ColumnMetadata toResultMetadata()
{
ColumnIdentifier fullName = getFullyQualifiedName();
ColumnMetadata forMetadata = column.withNewName(fullName);
if (isElementSelection())
{
if (forMetadata.type instanceof ListType)
forMetadata = forMetadata.withNewType(((ListType<?>) forMetadata.type).valueComparator());
else if (forMetadata.type instanceof SetType)
forMetadata = forMetadata.withNewType(((SetType<?>) forMetadata.type).nameComparator());
else if (forMetadata.type instanceof MapType)
forMetadata = forMetadata.withNewType(((MapType<?, ?>) forMetadata.type).valueComparator());
}
else if (isFieldSelection())
{
forMetadata = forMetadata.withNewType(getFieldSelectionType());
}
return forMetadata;
}
public ColumnSpecification getValueReceiver()
{
if (isElementSelection())
{
CollectionType.Kind collectionKind = ((CollectionType<?>) column.type).kind;
switch (collectionKind)
{
case LIST:
return Lists.valueSpecOf(column);
case MAP:
return Maps.valueSpecOf(column);
default:
throw new InvalidRequestException(String.format("Element selection not supported for column %s of type %s" ,
column.name, collectionKind));
}
}
else if (isFieldSelection())
{
return getFieldSelectionSpec();
}
return column;
}
public boolean isElementSelection()
{
return elementPath != null && column.type.isCollection();
}
public boolean isFieldSelection()
{
return fieldPath != null && column.type.isUDT();
}
private AbstractType<?> getFieldSelectionType()
{
assert isFieldSelection() : "No field selection type exists";
return getFieldSelectionType(column, fieldPath);
}
private static AbstractType<?> getFieldSelectionType(ColumnMetadata column, CellPath fieldPath)
{
return ((UserType) column.type).fieldType(fieldPath);
}
public ColumnSpecification getFieldSelectionSpec()
{
assert isFieldSelection() : "No field selection type exists";
int field = ByteBufferUtil.getUnsignedShort(fieldPath.get(0), 0);
return UserTypes.fieldSpecOf(column, field);
}
private CellPath bindCellPath(QueryOptions options)
{
if (fieldPath != null)
return fieldPath;
return elementPath != null ? CellPath.create(elementPath.bindAndGet(options)) : null;
}
public TxnReference toTxnReference(QueryOptions options)
{
Preconditions.checkState(elementPath == null || column.isComplex() || column.type.isFrozenCollection());
Preconditions.checkState(fieldPath == null || column.isComplex() || column.type.isUDT());
return new TxnReference(selectName, column, bindCellPath(options));
}
public ColumnIdentifier getFullyQualifiedName()
{
// TODO: Make this more user-friendly...
String path = fieldPath != null ? '.' + Bytes.toHexString(fieldPath.get(0)) : (elementPath == null ? "" : "[0x" + elementPath + ']');
String fullName = selectName.name() + '.' + column.name.toString() + path;
return new ColumnIdentifier(fullName, true);
}
public ColumnMetadata column()
{
return column;
}
public static class Raw extends Term.Raw
{
private final Selectable.RawIdentifier tuple;
private final Selectable.RawIdentifier selected;
private final Object fieldOrElement;
private boolean isResolved = false;
private TxnDataName tupleName;
private ColumnMetadata column;
private Term elementPath = null;
private CellPath fieldPath = null;
public Raw(Selectable.RawIdentifier tuple, Selectable.Raw selected, Object fieldOrElement)
{
Preconditions.checkArgument(tuple != null, "tuple is null");
Preconditions.checkArgument(selected == null || selected instanceof Selectable.RawIdentifier, "selected is not a Selectable.RawIdentifier: " + selected);
this.tuple = tuple;
this.selected = (Selectable.RawIdentifier) selected;
this.fieldOrElement = fieldOrElement;
}
public static Raw fromSelectable(Selectable.RawIdentifier tuple, Selectable.Raw selectable)
{
if (selectable == null)
return new RowDataReference.Raw(tuple, null, null);
// TODO: Ideally it would be nice not to have to make items in the Selectables public
if (selectable instanceof Selectable.WithFieldSelection.Raw)
{
Selectable.WithFieldSelection.Raw selection = (Selectable.WithFieldSelection.Raw) selectable;
return new RowDataReference.Raw(tuple, selection.selected, selection.field);
}
else if (selectable instanceof Selectable.WithElementSelection.Raw)
{
Selectable.WithElementSelection.Raw elementSelection = (Selectable.WithElementSelection.Raw) selectable;
return new RowDataReference.Raw(tuple, elementSelection.selected, elementSelection.element);
}
else if (selectable instanceof Selectable.RawIdentifier)
{
Selectable.RawIdentifier selection = (Selectable.RawIdentifier) selectable;
return new RowDataReference.Raw(tuple, selection, null);
}
throw new UnsupportedOperationException("Cannot create column reference from selectable: " + selectable);
}
private void resolveFinished()
{
isResolved = true;
}
public void resolveReference(Map<TxnDataName, ReferenceSource> sources)
{
if (isResolved)
return;
// root level name
tupleName = TxnDataName.user(tuple.toString());
ReferenceSource source = sources.get(tupleName);
checkNotNull(source, CANNOT_FIND_TUPLE_MESSAGE, tupleName.name());
if (selected == null)
{
resolveFinished();
return;
}
column = source.getColumn(selected.toString());
checkNotNull(column, COLUMN_NOT_IN_TUPLE_MESSAGE, selected.toString(), tupleName.name());
// TODO: confirm update partition key terms don't contain column references. This can't be done in prepare
// because there can be intermediate functions (ie: pk=row.v+1 or pk=_add(row.v, 5)). Need a recursive Term visitor
if (fieldOrElement == null)
{
resolveFinished();
return;
}
if (column.type.isCollection())
{
Term.Raw element = (Term.Raw) fieldOrElement;
elementPath = element.prepare(column.ksName, specForElementOrSlice(column));
}
else if (column.type.isUDT())
{
FieldIdentifier field = (FieldIdentifier) fieldOrElement;
UserType userType = (UserType) column.type;
fieldPath = userType.cellPathForField(field);
}
resolveFinished();
}
private ColumnSpecification specForElementOrSlice(ColumnSpecification receiver)
{
switch (((CollectionType<?>) receiver.type).kind)
{
case LIST: return Lists.indexSpecOf(receiver);
case SET: return Sets.valueSpecOf(receiver);
case MAP: return Maps.keySpecOf(receiver);
default: throw new AssertionError("Unknown collection type: " + receiver.type);
}
}
public void checkResolved()
{
if (!isResolved)
throw new IllegalStateException();
}
@Override
public AssignmentTestable.TestResult testAssignment(String keyspace, ColumnSpecification receiver)
{
checkResolved();
AbstractType<?> type = column.type;
if (elementPath != null)
{
CollectionType<?> collectionType = (CollectionType<?>) type;
type = collectionType.kind == CollectionType.Kind.SET ? collectionType.nameComparator() : collectionType.valueComparator();
}
else if (fieldPath != null)
{
type = RowDataReference.getFieldSelectionType(column, fieldPath);
}
return type.testAssignment(receiver.type);
}
@Override
public Term prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException
{
return prepare(keyspace, receiver, tupleName, column, elementPath, fieldPath);
}
public RowDataReference prepareAsReceiver()
{
checkResolved();
return new RowDataReference(tupleName, column, elementPath, fieldPath);
}
private RowDataReference prepare(String keyspace,
ColumnSpecification receiver,
TxnDataName selectName,
ColumnMetadata column,
Term elementPath,
CellPath fieldPath)
{
if (!testAssignment(keyspace, receiver).isAssignable())
throw new InvalidRequestException(String.format("Invalid reference type %s (%s) for \"%s\" of type %s",
column.type, column.name, receiver.name, receiver.type.asCQL3Type()));
return new RowDataReference(selectName, column, elementPath, fieldPath);
}
@Override
public String getText()
{
StringBuilder text = new StringBuilder(tuple.toString());
if (selected != null)
text.append('.').append(selected);
if (fieldOrElement != null)
{
if (fieldOrElement instanceof Term.Raw)
{
Term.Raw element = (Term.Raw) fieldOrElement;
text.append('.').append(element.getText());
}
else if (fieldOrElement instanceof FieldIdentifier)
{
FieldIdentifier field = (FieldIdentifier) fieldOrElement;
text.append('.').append(field);
}
else
{
throw new IllegalStateException("Field or element is neither a raw term nor a field identifier");
}
}
return text.toString();
}
@Override
public AbstractType<?> getExactTypeIfKnown(String keyspace)
{
checkResolved();
return column.type;
}
public ColumnMetadata column()
{
return column;
}
}
public interface ReferenceSource
{
ColumnMetadata getColumn(String name);
}
}

View File

@ -0,0 +1,52 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.cql3.transactions;
import java.util.HashSet;
import java.util.Set;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.statements.SelectStatement;
import org.apache.cassandra.schema.ColumnMetadata;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkTrue;
public class SelectReferenceSource implements RowDataReference.ReferenceSource
{
public static final String COLUMN_NOT_IN_SELECT_MESSAGE = "%s refererences a column not included in the select";
private final SelectStatement statement;
public SelectReferenceSource(SelectStatement statement)
{
this.statement = statement;
}
@Override
public ColumnMetadata getColumn(String name)
{
ColumnMetadata column = statement.table.getColumn(new ColumnIdentifier(name, true));
if (column != null)
{
Set<ColumnMetadata> selectedColumns = new HashSet<>(statement.getSelection().getColumns());
checkTrue(selectedColumns.contains(column), COLUMN_NOT_IN_SELECT_MESSAGE, statement);
}
return column;
}
}

View File

@ -61,7 +61,7 @@ public class DeletionTime implements Comparable<DeletionTime>, IMeasurableMemory
// Do not use. This is a perf optimization where some data structures known to hold valid uints are allowed to use it.
// You should use 'build' instead to not workaround validations, corruption detections, etc
static DeletionTime buildUnsafeWithUnsignedInteger(long markedForDeleteAt, int localDeletionTimeUnsignedInteger)
public static DeletionTime buildUnsafeWithUnsignedInteger(long markedForDeleteAt, int localDeletionTimeUnsignedInteger)
{
return CassandraUInt.compare(Cell.MAX_DELETION_TIME_UNSIGNED_INTEGER, localDeletionTimeUnsignedInteger) < 0
? new InvalidDeletionTime(markedForDeleteAt)

View File

@ -230,6 +230,16 @@ public class MutableDeletionInfo implements DeletionInfo
return this;
}
public DeletionInfo updateAllTimestampAndLocalDeletionTime(long timestamp, int localDeletionTime)
{
if (partitionDeletion.markedForDeleteAt() != Long.MIN_VALUE)
partitionDeletion = DeletionTime.buildUnsafeWithUnsignedInteger(timestamp, localDeletionTime);
if (ranges != null)
ranges.updateAllTimestampAndLocalDeletionTime(timestamp, localDeletionTime);
return this;
}
@Override
public boolean equals(Object o)
{

View File

@ -29,7 +29,7 @@ import org.apache.cassandra.utils.bytecomparable.ByteSource;
public interface PartitionPosition extends RingPosition<PartitionPosition>, ByteComparable
{
public static enum Kind
public enum Kind
{
// Only add new values to the end of the enum, the ordinal is used
// during serialization

View File

@ -328,6 +328,15 @@ public class RangeTombstoneList implements Iterable<RangeTombstone>, IMeasurable
markedAts[i] = timestamp;
}
public void updateAllTimestampAndLocalDeletionTime(long timestamp, int localDeletionTime)
{
for (int i = 0; i < size; i++)
{
markedAts[i] = timestamp;
delTimesUnsignedIntegers[i] = localDeletionTime;
}
}
private RangeTombstone rangeTombstone(int idx)
{
return new RangeTombstone(Slice.make(starts[idx], ends[idx]), DeletionTime.buildUnsafeWithUnsignedInteger(markedAts[idx], delTimesUnsignedIntegers[idx]));

View File

@ -434,6 +434,24 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
isTrackingWarnings());
}
public SinglePartitionReadCommand withNowInSec(int nowInSec)
{
return new SinglePartitionReadCommand(serializedAtEpoch(),
isDigestQuery(),
digestVersion(),
acceptsTransient(),
metadata(),
nowInSec,
columnFilter(),
rowFilter(),
limits(),
partitionKey(),
clusteringIndexFilter(),
indexQueryPlan(),
isTrackingWarnings(),
dataRange());
}
@Override
public DecoratedKey partitionKey()
{
@ -492,7 +510,9 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
metric.readLatency.addNano(latencyNanos);
}
protected UnfilteredPartitionIterator queryStorage(final ColumnFamilyStore cfs, ReadExecutionController executionController)
@VisibleForTesting
@SuppressWarnings("resource") // we close the created iterator through closing the result of this method (and SingletonUnfilteredPartitionIterator ctor cannot fail)
public UnfilteredPartitionIterator queryStorage(final ColumnFamilyStore cfs, ReadExecutionController executionController)
{
// skip the row cache and go directly to sstables/memtable if repaired status of
// data is being tracked. This is only requested after an initial digest mismatch

View File

@ -79,6 +79,7 @@ import org.apache.cassandra.gms.VersionedValue;
import org.apache.cassandra.io.sstable.SSTableId;
import org.apache.cassandra.io.sstable.SequenceBasedSSTableId;
import org.apache.cassandra.io.util.DataInputBuffer;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputBuffer;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.RebufferingInputStream;
@ -1855,9 +1856,9 @@ public final class SystemKeyspace
if (!previous.equals(NULL_VERSION.toString()) && !previous.equals(next))
{
List<String> entities = new ArrayList<>();
for (String keyspace : SchemaConstants.LOCAL_SYSTEM_KEYSPACE_NAMES)
for (Keyspace keyspace : Keyspace.system())
{
for (ColumnFamilyStore cfs : Keyspace.open(keyspace).getColumnFamilyStores())
for (ColumnFamilyStore cfs : keyspace.getColumnFamilyStores())
entities.add(cfs.getKeyspaceTableName());
}
@ -1934,12 +1935,10 @@ public final class SystemKeyspace
@SuppressWarnings("unchecked")
private static Range<Token> byteBufferToRange(ByteBuffer rawRange, IPartitioner partitioner)
{
try
try (DataInputPlus.DataInputStreamPlus in = new DataInputBuffer(ByteBufferUtil.getArray(rawRange)))
{
// See rangeToBytes above for why version is 0.
return (Range<Token>) Range.tokenSerializer.deserialize(new DataInputBuffer(ByteBufferUtil.getArray(rawRange)),
partitioner,
0);
return (Range<Token>) Range.tokenSerializer.deserialize(in, partitioner, 0);
}
catch (IOException e)
{

View File

@ -17,6 +17,10 @@
*/
package org.apache.cassandra.db;
/**
* Identifier for what type of operation timed out. This type is driver facing as a String, but some drivers convert
* this to an enum, meaning any changes to this type require protocol changes and driver support.
*/
public enum WriteType
{
SIMPLE,
@ -26,5 +30,6 @@ public enum WriteType
BATCH_LOG,
CAS,
VIEW,
CDC;
CDC
//TODO update client protocol to support "TRANSACTION"
}

View File

@ -197,20 +197,6 @@ public class ClusteringIndexNamesFilter extends AbstractClusteringIndexFilter
return sb.toString();
}
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ClusteringIndexNamesFilter that = (ClusteringIndexNamesFilter) o;
return Objects.equals(clusterings, that.clusterings) &&
Objects.equals(reversed, that.reversed);
}
public int hashCode()
{
return Objects.hash(clusterings, reversed);
}
public Kind kind()
{
return Kind.NAMES;

View File

@ -682,7 +682,7 @@ public abstract class ColumnFilter
SortedSetMultimap<ColumnIdentifier, ColumnSubselection> subSelections)
{
assert queried != null;
assert fetched.includes(queried);
assert fetched.includes(queried) : String.format("Queries columns %s are not included in the fetch strategy %s", queried, fetched);
this.fetchingStrategy = fetchingStrategy;
this.queried = queried;

View File

@ -440,7 +440,7 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer>, Assignm
return false;
}
public AbstractType<?> freeze()
public AbstractType<T> freeze()
{
return this;
}

View File

@ -25,6 +25,7 @@ import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.UUID;
import accord.utils.Invariants;
import org.apache.cassandra.db.Digest;
import org.apache.cassandra.db.TypeSizes;
import org.apache.cassandra.io.util.DataInputPlus;
@ -107,6 +108,7 @@ public class ByteArrayAccessor implements ValueAccessor<byte[]>
@Override
public byte[] slice(byte[] input, int offset, int length)
{
Invariants.checkArgument(offset + length <= input.length);
return Arrays.copyOfRange(input, offset, offset + length);
}

View File

@ -37,9 +37,9 @@ import org.apache.cassandra.exceptions.SyntaxException;
import org.apache.cassandra.serializers.ListSerializer;
import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.transport.ProtocolVersion;
import org.apache.cassandra.utils.JsonUtils;
import org.apache.cassandra.utils.TimeUUID;
import org.apache.cassandra.transport.ProtocolVersion;
import org.apache.cassandra.utils.bytecomparable.ByteComparable.Version;
import org.apache.cassandra.utils.bytecomparable.ByteSource;
@ -52,7 +52,7 @@ public class ListType<T> extends CollectionType<List<T>>
private static final ConcurrentHashMap<AbstractType<?>, ListType> frozenInstances = new ConcurrentHashMap<>();
private final AbstractType<T> elements;
public final ListSerializer<T> serializer;
private final ListSerializer<T> serializer;
private final boolean isMultiCell;
public static ListType<?> getInstance(TypeParser parser) throws ConfigurationException, SyntaxException
@ -131,7 +131,7 @@ public class ListType<T> extends CollectionType<List<T>>
}
@Override
public AbstractType<?> freeze()
public ListType<T> freeze()
{
// freeze elements to match org.apache.cassandra.cql3.CQL3Type.Raw.RawCollection.freeze
return isMultiCell ? getInstance(this.elements.freeze(), false) : this;

View File

@ -44,10 +44,10 @@ import org.apache.cassandra.serializers.MapSerializer;
import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.transport.ProtocolVersion;
import org.apache.cassandra.utils.JsonUtils;
import org.apache.cassandra.utils.Pair;
import org.apache.cassandra.utils.bytecomparable.ByteComparable.Version;
import org.apache.cassandra.utils.bytecomparable.ByteSource;
import org.apache.cassandra.utils.bytecomparable.ByteSourceInverse;
import org.apache.cassandra.utils.Pair;
public class MapType<K, V> extends CollectionType<Map<K, V>>
{
@ -153,7 +153,7 @@ public class MapType<K, V> extends CollectionType<Map<K, V>>
}
@Override
public AbstractType<?> freeze()
public MapType<K, V> freeze()
{
// freeze key/value to match org.apache.cassandra.cql3.CQL3Type.Raw.RawCollection.freeze
return isMultiCell ? getInstance(this.keys.freeze(), this.values.freeze(), false) : this;

View File

@ -18,7 +18,13 @@
package org.apache.cassandra.db.marshal;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
@ -117,7 +123,7 @@ public class SetType<T> extends CollectionType<Set<T>>
}
@Override
public AbstractType<?> freeze()
public SetType<T> freeze()
{
// freeze elements to match org.apache.cassandra.cql3.CQL3Type.Raw.RawCollection.freeze
return isMultiCell ? getInstance(this.elements.freeze(), false) : this;

View File

@ -24,6 +24,7 @@ import java.util.stream.Collectors;
import javax.annotation.Nullable;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import org.slf4j.Logger;
@ -136,6 +137,12 @@ public class UserType extends TupleType implements SchemaElement
return type(i);
}
public AbstractType<?> fieldType(CellPath path)
{
int field = ByteBufferUtil.getUnsignedShort(path.get(0), 0);
return fieldType(field);
}
public List<AbstractType<?>> fieldTypes()
{
return types;
@ -146,6 +153,11 @@ public class UserType extends TupleType implements SchemaElement
return fieldNames.get(i);
}
public FieldIdentifier fieldName(CellPath path)
{
return fieldNames.get(fieldPosition(path));
}
public String fieldNameAsString(int i)
{
return stringFieldNames.get(i);
@ -166,6 +178,11 @@ public class UserType extends TupleType implements SchemaElement
return fieldNames.indexOf(fieldName);
}
public int fieldPosition(CellPath path)
{
return Preconditions.checkElementIndex(ByteBufferUtil.getUnsignedShort(path.get(0), 0), fieldNames.size());
}
public CellPath cellPathForField(FieldIdentifier fieldName)
{
// we use the field position instead of the field name to allow for field renaming in ALTER TYPE statements
@ -177,7 +194,7 @@ public class UserType extends TupleType implements SchemaElement
return ShortType.instance;
}
public ByteBuffer serializeForNativeProtocol(Iterator<Cell<?>> cells, ProtocolVersion protocolVersion)
public ByteBuffer serializeForNativeProtocol(Iterator<Cell<?>> cells)
{
assert isMultiCell;

View File

@ -386,10 +386,10 @@ public abstract class AbstractBTreePartition implements Partition, Iterable<Row>
@Override
public boolean equals(Object obj)
{
if (!(obj instanceof PartitionUpdate))
if (!(obj instanceof AbstractBTreePartition))
return false;
PartitionUpdate that = (PartitionUpdate) obj;
AbstractBTreePartition that = (AbstractBTreePartition) obj;
BTreePartitionData a = this.holder(), b = that.holder();
return partitionKey.equals(that.partitionKey)
&& metadata().id.equals(that.metadata().id)

View File

@ -24,6 +24,7 @@ import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.DeletionInfo;
import org.apache.cassandra.db.RegularAndStaticColumns;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.utils.btree.BTree;
public class FilteredPartition extends ImmutableBTreePartition
{
@ -43,6 +44,11 @@ public class FilteredPartition extends ImmutableBTreePartition
return new FilteredPartition(iterator);
}
public Row getAtIdx(int idx)
{
return BTree.findByIndex(holder.tree, idx);
}
public RowIterator rowIterator()
{
final Iterator<Row> iter = iterator();

View File

@ -37,6 +37,7 @@ import org.apache.cassandra.db.filter.ColumnFilter;
public interface Partition
{
public TableMetadata metadata();
public DecoratedKey partitionKey();
public DeletionTime partitionLevelDeletion();

View File

@ -249,7 +249,8 @@ public class PartitionUpdate extends AbstractBTreePartition
}
protected boolean canHaveShadowedData()
@Override
public boolean canHaveShadowedData()
{
return canHaveShadowedData;
}
@ -586,6 +587,15 @@ public class PartitionUpdate extends AbstractBTreePartition
return new PartitionUpdate(metadata, metadata.epoch, key, holder, deletionInfo, canHaveShadowedData);
}
@Override
public boolean equals(Object obj)
{
if (!(obj instanceof PartitionUpdate))
return false;
return super.equals(obj);
}
/**
* Interface for building partition updates geared towards human.
* <p>
@ -914,6 +924,15 @@ public class PartitionUpdate extends AbstractBTreePartition
this(metadata, key, columns, initialRowCapacity, canHaveShadowedData, Rows.EMPTY_STATIC_ROW, MutableDeletionInfo.live(), BTree.empty());
}
public Builder(TableMetadata metadata,
DecoratedKey key,
RegularAndStaticColumns columns,
Row staticRow,
int initialRowCapacity)
{
this(metadata, key, columns, initialRowCapacity, true, staticRow, MutableDeletionInfo.live(), BTree.empty());
}
private Builder(TableMetadata metadata,
DecoratedKey key,
RegularAndStaticColumns columns,
@ -1090,6 +1109,14 @@ public class PartitionUpdate extends AbstractBTreePartition
return this;
}
public Builder updateAllTimestampAndLocalDeletionTime(long newTimestamp, int newLocalDeletionTime)
{
deletionInfo.updateAllTimestampAndLocalDeletionTime(newTimestamp - 1, newLocalDeletionTime);
tree = BTree.<Row, Row>transformAndFilter(tree, (x) -> x.updateAllTimestampAndLocalDeletionTime(newTimestamp, newLocalDeletionTime));
staticRow = this.staticRow.updateAllTimestampAndLocalDeletionTime(newTimestamp, newLocalDeletionTime);
return this;
}
@Override
public String toString()
{

View File

@ -117,6 +117,13 @@ public abstract class AbstractCell<V> extends Cell<V>
return new BufferCell(column, isTombstone() ? newTimestamp - 1 : newTimestamp, ttl(), localDeletionTime(), buffer(), path());
}
@Override
public ColumnData updateAllTimestampAndLocalDeletionTime(long newTimestamp, int newLocalDeletionTime)
{
long localDeletionTime = localDeletionTime() != NO_DELETION_TIME ? newLocalDeletionTime : NO_DELETION_TIME;
return new BufferCell(column, isTombstone() ? newTimestamp - 1 : newTimestamp, ttl(), localDeletionTime, buffer(), path());
}
public int dataSize()
{
CellPath path = path();

View File

@ -267,7 +267,7 @@ public class BTreeRow extends AbstractRow
public Cell<?> getCell(ColumnMetadata c)
{
assert !c.isComplex();
assert !c.isComplex(): String.format("Column %s.%s#%s", c.ksName, c.cfName, c.name);
return (Cell<?>) BTree.<Object>find(btree, ColumnMetadata.asymmetricColumnDataComparator, c);
}
@ -445,6 +445,18 @@ public class BTreeRow extends AbstractRow
return transformAndFilter(newInfo, newDeletion, (cd) -> cd.updateAllTimestamp(newTimestamp));
}
@Override
public Row updateAllTimestampAndLocalDeletionTime(long newTimestamp, int newLocalDeletionTime)
{
LivenessInfo newInfo = primaryKeyLivenessInfo.isEmpty() ? primaryKeyLivenessInfo : primaryKeyLivenessInfo.withUpdatedTimestampAndLocalDeletionTime(newTimestamp, newLocalDeletionTime);
// If the deletion is shadowable and the row has a timestamp, we'll forced the deletion timestamp to be less than the row one, so we
// should get rid of said deletion.
Deletion newDeletion = deletion.isLive() || (deletion.isShadowable() && !primaryKeyLivenessInfo.isEmpty())
? Deletion.LIVE
: new Deletion(DeletionTime.buildUnsafeWithUnsignedInteger(newTimestamp - 1, newLocalDeletionTime), deletion.isShadowable());
return transformAndFilter(newInfo, newDeletion, (cd) -> cd.updateAllTimestampAndLocalDeletionTime(newTimestamp, newLocalDeletionTime));
}
public Row withRowDeletion(DeletionTime newDeletion)
{
// Note that:

View File

@ -284,6 +284,7 @@ public abstract class ColumnData implements IMeasurableMemory
* This exists for the Paxos path, see {@link PartitionUpdate#updateAllTimestamp} for additional details.
*/
public abstract ColumnData updateAllTimestamp(long newTimestamp);
public abstract ColumnData updateAllTimestampAndLocalDeletionTime(long newTimestamp, int newLocalDeletionTime);
public abstract ColumnData markCounterLocalToBeCleared();

View File

@ -264,6 +264,13 @@ public class ComplexColumnData extends ColumnData implements Iterable<Cell<?>>
return transformAndFilter(newDeletion, (cell) -> (Cell<?>) cell.updateAllTimestamp(newTimestamp));
}
@Override
public ColumnData updateAllTimestampAndLocalDeletionTime(long newTimestamp, int newLocalDeletionTime)
{
DeletionTime newDeletion = complexDeletion.isLive() ? complexDeletion : DeletionTime.buildUnsafeWithUnsignedInteger(newTimestamp - 1, newLocalDeletionTime);
return transformAndFilter(newDeletion, (cell) -> (Cell<?>) cell.updateAllTimestampAndLocalDeletionTime(newTimestamp, newLocalDeletionTime));
}
public long maxTimestamp()
{
long timestamp = complexDeletion.markedForDeleteAt();

View File

@ -299,6 +299,8 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
*/
public Row updateAllTimestamp(long newTimestamp);
public Row updateAllTimestampAndLocalDeletionTime(long newTimestamp, int newLocalDeletionTime);
/**
* Returns a copy of this row with the new deletion as row deletion if it is more recent
* than the current row deletion.

View File

@ -0,0 +1,84 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.db.virtual;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import com.google.common.annotations.VisibleForTesting;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.statements.schema.CreateTableStatement;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.accord.AccordService;
import org.apache.cassandra.service.accord.IAccordService;
public class AccordVirtualTables
{
private AccordVirtualTables()
{
}
public static Collection<VirtualTable> getAll(String keyspace)
{
if (!DatabaseDescriptor.getAccordTransactionsEnabled())
return Collections.emptyList();
return Arrays.asList(
new Epoch(keyspace)
);
}
@VisibleForTesting
public static final class Epoch extends AbstractVirtualTable
{
protected Epoch(String keyspace)
{
super(parse(keyspace, "Accord Epochs",
"CREATE TABLE accord_epochs(\n" +
" epoch bigint,\n" +
" PRIMARY KEY ( (epoch) )" +
")"));
}
@Override
public DataSet data()
{
IAccordService accord = AccordService.instance();
accord.createEpochFromConfigUnsafe();
long epoch = accord.currentEpoch();
SimpleDataSet result = new SimpleDataSet(metadata());
result.row(epoch);
return result;
}
}
private static TableMetadata parse(String keyspace, String comment, String query)
{
return CreateTableStatement.parse(query, keyspace)
.comment(comment)
.kind(TableMetadata.Kind.VIRTUAL)
.build();
}
}

View File

@ -69,6 +69,7 @@ public final class SystemViewsKeyspace extends VirtualKeyspace
.addAll(LocalRepairTables.getAll(VIRTUAL_VIEWS))
.addAll(CIDRFilteringMetricsTable.getAll(VIRTUAL_VIEWS))
.addAll(StorageAttachedIndexTables.getAll(VIRTUAL_VIEWS))
.addAll(AccordVirtualTables.getAll(VIRTUAL_VIEWS))
.build());
}
}

View File

@ -0,0 +1,89 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.dht;
import java.math.BigInteger;
import accord.api.RoutingKey;
import accord.primitives.Ranges;
import accord.utils.Invariants;
import org.apache.cassandra.service.accord.api.AccordRoutingKey;
import static accord.utils.Invariants.checkArgument;
import static java.math.BigInteger.ONE;
import static java.math.BigInteger.ZERO;
public class AccordBytesSplitter extends AccordSplitter
{
final int byteLength;
protected AccordBytesSplitter(Ranges ranges)
{
int bytesLength = 0;
for (accord.primitives.Range range : ranges)
{
bytesLength = Integer.max(bytesLength, byteLength(range.start()));
bytesLength = Integer.max(bytesLength, byteLength(range.end()));
}
this.byteLength = bytesLength;
}
@Override
BigInteger minimumValue()
{
return ZERO;
}
@Override
BigInteger maximumValue()
{
return ONE.shiftLeft(8 * byteLength).subtract(ONE);
}
@Override
BigInteger valueForToken(Token token)
{
byte[] bytes = ((ByteOrderedPartitioner.BytesToken) token).token;
checkArgument(bytes.length <= byteLength);
BigInteger value = ZERO;
for (int i = 0 ; i < bytes.length ; ++i)
value = value.add(BigInteger.valueOf(bytes[i] & 0xffL).shiftLeft((byteLength - 1 - i) * 8));
return value;
}
@Override
Token tokenForValue(BigInteger value)
{
Invariants.checkArgument(value.compareTo(ZERO) >= 0);
byte[] bytes = new byte[byteLength];
for (int i = 0 ; i < bytes.length ; ++i)
bytes[i] = value.shiftRight((byteLength - 1 - i) * 8).byteValue();
return new ByteOrderedPartitioner.BytesToken(bytes);
}
private static int byteLength(RoutingKey routingKey)
{
return byteLength(((AccordRoutingKey) routingKey).token());
}
private static int byteLength(Token token)
{
return ((ByteOrderedPartitioner.BytesToken) token).token.length;
}
}

View File

@ -0,0 +1,103 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.dht;
import java.math.BigInteger;
import accord.local.ShardDistributor;
import org.apache.cassandra.service.accord.TokenRange;
import org.apache.cassandra.service.accord.api.AccordRoutingKey;
import org.apache.cassandra.service.accord.api.AccordRoutingKey.SentinelKey;
import org.apache.cassandra.service.accord.api.AccordRoutingKey.TokenKey;
import static java.math.BigInteger.ZERO;
public abstract class AccordSplitter implements ShardDistributor.EvenSplit.Splitter<BigInteger>
{
abstract BigInteger valueForToken(Token token);
abstract Token tokenForValue(BigInteger value);
abstract BigInteger minimumValue();
abstract BigInteger maximumValue();
@Override
public BigInteger sizeOf(accord.primitives.Range range)
{
// note: minimum value
BigInteger start = range.start() instanceof SentinelKey ? minimumValue() : valueForToken(((AccordRoutingKey)range.start()).token());
BigInteger end = range.end() instanceof SentinelKey ? maximumValue() : valueForToken(((AccordRoutingKey)range.end()).token());
return end.subtract(start);
}
@Override
public accord.primitives.Range subRange(accord.primitives.Range range, BigInteger startOffset, BigInteger endOffset)
{
AccordRoutingKey startBound = (AccordRoutingKey)range.start();
AccordRoutingKey endBound = (AccordRoutingKey)range.end();
BigInteger start = startBound instanceof SentinelKey ? minimumValue() : valueForToken(startBound.token());
BigInteger end = endBound instanceof SentinelKey ? maximumValue() : valueForToken(endBound.token());
BigInteger sizeOfRange = end.subtract(start);
String keyspace = startBound.keyspace();
return new TokenRange(startOffset.equals(ZERO) ? startBound : new TokenKey(keyspace, tokenForValue(start.add(startOffset))),
endOffset.equals(sizeOfRange) ? endBound : new TokenKey(keyspace, tokenForValue(start.add(endOffset))));
}
@Override
public BigInteger zero()
{
return ZERO;
}
@Override
public BigInteger add(BigInteger a, BigInteger b)
{
return a.add(b);
}
@Override
public BigInteger subtract(BigInteger a, BigInteger b)
{
return a.subtract(b);
}
@Override
public BigInteger divide(BigInteger a, int i)
{
return a.divide(BigInteger.valueOf(i));
}
@Override
public BigInteger multiply(BigInteger a, int i)
{
return a.multiply(BigInteger.valueOf(i));
}
@Override
public int min(BigInteger v, int i)
{
return v.min(BigInteger.valueOf(i)).intValue();
}
@Override
public int compare(BigInteger a, BigInteger b)
{
return a.compareTo(b);
}
}

View File

@ -17,6 +17,7 @@
*/
package org.apache.cassandra.dht;
import accord.primitives.Ranges;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.db.BufferDecoratedKey;
@ -44,6 +45,7 @@ import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Function;
import com.google.common.collect.Maps;
@ -128,6 +130,12 @@ public class ByteOrderedPartitioner implements IPartitioner
return token;
}
@Override
public int tokenHash()
{
return hashCode();
}
@Override
public double size(Token next)
{
@ -138,8 +146,51 @@ public class ByteOrderedPartitioner implements IPartitioner
@Override
public Token nextValidToken()
{
throw new UnsupportedOperationException(String.format("Token type %s does not support token allocation.",
getClass().getSimpleName()));
// find first byte we can increment
int i = token.length - 1;
while (i >= 0)
{
if (token[i] != -1)
break;
--i;
}
if (i == -1)
return new BytesToken(Arrays.copyOf(token, token.length + 1));
// increment and fill remainder with zeros
byte[] newToken = token.clone();
++newToken[i];
Arrays.fill(newToken, i + 1, newToken.length, (byte)0);
return new BytesToken(newToken);
}
@Override
public Token decreaseSlightly()
{
if (token.length == 0)
throw new IndexOutOfBoundsException("Cannot create a smaller token the MINIMUM");
// find first byte we can decrement
int i = token.length - 1;
while (i >= 0)
{
if (token[i] != 0)
break;
--i;
}
if (i == -1)
{
byte[] newToken = Arrays.copyOf(token, token.length - 1);
if (newToken.length > 0)
newToken[newToken.length - 1] = (byte)-1;
return new BytesToken(newToken);
}
// decrement and fill remainder with -1
byte[] newToken = token.clone();
--newToken[i];
Arrays.fill(newToken, i + 1, newToken.length, (byte)-1);
return new BytesToken(newToken);
}
}
@ -339,4 +390,10 @@ public class ByteOrderedPartitioner implements IPartitioner
{
return BytesType.instance;
}
@Override
public Function<Ranges, AccordSplitter> accordSplitter()
{
return AccordBytesSplitter::new;
}
}

View File

@ -80,4 +80,11 @@ abstract class ComparableObjectToken<C extends Comparable<C>> extends Token
throw new UnsupportedOperationException(String.format("Token type %s does not support token allocation.",
getClass().getSimpleName()));
}
@Override
public Token decreaseSlightly()
{
throw new UnsupportedOperationException(String.format("Token type %s does not support token allocation.",
getClass().getSimpleName()));
}
}

View File

@ -22,6 +22,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import java.util.function.Function;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.DecoratedKey;
@ -144,6 +145,13 @@ public interface IPartitioner
return Optional.empty();
}
Function<accord.primitives.Ranges, AccordSplitter> accordSplitter();
default boolean isFixedLength()
{
return false;
}
default public int getMaxTokenSize()
{
return Integer.MIN_VALUE;

View File

@ -22,7 +22,9 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.function.Function;
import accord.primitives.Ranges;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.CachedHashDecoratedKey;
import org.apache.cassandra.db.marshal.AbstractType;
@ -174,6 +176,12 @@ public class LocalPartitioner implements IPartitioner
return prime + token.hashCode();
}
@Override
public int tokenHash()
{
return hashCode();
}
@Override
public boolean equals(Object obj)
{
@ -203,4 +211,10 @@ public class LocalPartitioner implements IPartitioner
return EMPTY_SIZE + ObjectSizes.sizeOnHeapOf(token);
}
}
@Override
public Function<Ranges, AccordSplitter> accordSplitter()
{
return AccordBytesSplitter::new;
}
}

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